web-dev-qa-db-fra.com

Comment puis-je utiliser tous les fichiers d'un type donné s'ils n'ont peut-être pas la bonne extension?

Cette question est posée par un court script que j'ai trouvé dans un magazine Linux. Pour prouver que je ne l'ai pas inventé, voici une photo:

quite awful code sample

J'aimerais écrire à l'éditeur de cette publication pour lui expliquer ce qui ne va pas et comment mieux l'écrire.

Le script tente de capturer les fichiers jpeg dans une variable, de sorte que quelque chose (compression utilisant lepton) puisse être fait avec eux.

for jpeg in `echo "$(file $(find ./ ) |
   grep JPEG | cut -f 1 -d ':')"`
  do
     /path/to/command "$jpeg"
...

Apparemment, dans ce cas, nous ne pouvons pas faire confiance aux fichiers portant l’extension .jpg, nous ne pouvons donc pas les attraper avec quelque chose comme:

for f in *.JPG *.jpg *.JPEG *.jpeg ; do ...

parce que l'écrivain a utilisé file pour vérifier leur type, mais si les noms de fichiers ne peuvent pas avoir une extension raisonnable, alors je ne vois pas comment nous pouvons leur faire confiance pour ne pas être -rf * ou (; \ $!| ou avoir des nouvelles lignes ou autre chose.

Comment puis-je capturer correctement des fichiers dans une variable par type avec for ou while, ou peut-être éviter de le faire en utilisant find avec -exec, ou une autre méthode?

Bonus pour avoir un aperçu et une démonstration de ce qui ne va pas avec le code dans l'image.

J'ai étiqueté cette question avec [bash] puisqu'il s'agit d'un script bash, mais si vous avez envie de répondre à une façon de le faire qui ne l'utilise pas, n'hésitez pas à le faire.

6
Zanna

Code premier:

Faisons ceci avec les globs spéciaux de Bash et une boucle for:

#!/bin/bash
shopt -s globstar dotglob

for f in ./** ; do 
    if file -b -- "$f" | grep -q '^JPEG image data,' ; then 

        # do whatever you want with the JPEG file "$f" in here:
        md5sum -- "$f"

    fi
done

Explication:

Tout d’abord, nous devons rendre les globes Bash plus utiles en activant les options de shell globstar et dotglob Shell. Voici leur description de man bash dans la section Shell BUILTIN COMMANDS sur shopt:

 dotglob 
    If set, bash includes filenames beginning with a `.' in the results of 
    pathname expansion.
 globstar
    If set, the pattern ** used in a pathname expansion context will match 
    all files and zero or more directories and subdirectories. If the pattern
    is followed by a /, only directories and subdirectories match.

Ensuite, nous utilisons ce nouveau "glob récursif" ./** dans une boucle for pour parcourir tous les fichiers et dossiers du répertoire en cours et tous ses sous-répertoires. Veuillez toujours utiliser des chemins absolus ou des chemins relatifs explicites commençant par ./ ou ../ dans vos globs, pas seulement **, pour éviter les problèmes de noms de fichiers spéciaux tels que ~.

Maintenant, nous testons chaque nom de fichier (et de dossier) avec la commande file pour son contenu. L'option -b l'empêche d'imprimer à nouveau le nom du fichier avant la chaîne d'informations sur le contenu, ce qui rend le filtrage plus sûr.

Nous savons maintenant que les informations de contenu de tous les fichiers JPG/JPEG valides doivent commencer par JPEG image data,, ce qui correspond au test de la sortie de file avec grep. Nous utilisons l'option -q pour supprimer toute sortie, car nous ne nous intéressons qu'au code de sortie de grep, qui indique si le motif correspond ou non.

Si cela correspond, le code à l'intérieur du bloc if/then sera exécuté. Nous pouvons faire tout ce que nous voulons ici. Le nom de fichier JPEG actuel est disponible dans la variable shell $f. Nous devons simplement nous assurer de toujours le mettre entre guillemets pour éviter l’évaluation accidentelle de noms de fichiers contenant des caractères spéciaux tels que des espaces, des nouvelles lignes ou des symboles. Il est également généralement préférable de le séparer des autres arguments en le plaçant après --, ce qui oblige la plupart des commandes à l’interpréter comme un nom de fichier, même s’il ressemble à -v ou --help autrement. être interprété comme une option.


Question bonus:

Il est temps de faire sauter du code, pour la science! Voici la version de votre question/livre:

for jpeg in `echo "$(file $(find ./ ) 
    | grep JPEG | cut -f 1 -d ':')"`
do
     /path/to/command "$jpeg"
done

Tout d’abord, permettez-moi de mentionner la complexité de leur rédaction. Nous avons 4 niveaux de sous-shell imbriqués, utilisant des syntaxes de substitution de commandes mixtes (`` et $()), qui sont simplement nécessaires en raison de l'utilisation incorrecte/sous-optimale de find.

Ici, find répertorie uniquement tous les fichiers et affiche leur nom, un par ligne. Ensuite, la sortie complète est passée à file pour examiner chacune d’elles. Mais attendez! Un nom de fichier par ligne? Qu'en est-il des noms de fichiers contenant des nouvelles lignes? Bon, ceux qui vont le casser!

$ ls --escape ne*ne
new\nline
$ file $(find . -name 'ne*ne' )
./new: cannot open `./new' (No such file or directory)
line:  cannot open `line' (No such file or directory)

En fait, même des espaces simples le séparent aussi, car ceux-ci sont également traités comme des séparateurs par file. Vous ne pouvez même pas citer la "$(find ./ )" ici comme solution, car cela indiquerait alors la sortie multiligne entière comme un argument de nom de fichier unique.

$ ls simple*
simple spaces.jpg
$ file $(find ./ -name 'simple*')
./simple:   cannot open `./simple' (No such file or directory)
spaces.jpg: cannot open `spaces.jpg' (No such file or directory)

Ensuite, la sortie file est analysée avec grep JPEG. Ne pensez-vous pas qu'il est un peu facile de tromper un motif aussi simple, d'autant plus que la sortie de plain file contient toujours le nom du fichier? Fondamentalement, tout ce qui contient "JPEG" dans son nom de fichier déclenchera une correspondance, quoi qu’il contienne.

$ echo "to be or not to be" > IAmNoJPEG.txt
$ file IAmNoJPEG.txt | grep JPEG
IAmNoJPEG.txt: ASCII text

Donc, nous avons la sortie file de tous les fichiers JPEG (ou ceux qui prétendent en être un), maintenant ils traitent toutes les lignes avec cut pour extraire le nom de fichier original de la première colonne, séparés par deux points ... Devinez quoi, essayons ceci sur un fichier avec deux points dans son nom:

$ ls colon*
colons:evil.jpeg
$ file colon* | grep JPEG | cut -f 1 -d ':'
colons

Donc, pour conclure, l’approche de votre livre fonctionne, mais seulement si tous les fichiers qu’il vérifie ne contiennent aucun espace, retour à la ligne, point-virgule et probablement d’autres caractères spéciaux et ne contiennent pas la chaîne "JPEG" où que ce soit dans leurs noms de fichiers. C’est aussi un peu moche, mais comme la beauté est dans l’œil du spectateur, je ne vais pas en parler.

5
Byte Commander

Vous avez find et vérifiez également avec la commande file pour son type mime.

find . -type f -exec file --mime-type -b '{}' +

Ou pour le rendre complet comme suit:

find . -type f -exec sh -c '
    file --mime-type -b "$0" | grep -q "aPATTERN" && printf "$0\n"
' {} \;

Ou l'option identify des packages ImageMagic .

find -type f -print0 | xargs -0 identify
1
αғsнιη