228 votes

Comment exécuter une commande, par exemple chmod, pour chaque ligne d'un fichier ?

Par exemple, en ce moment, j'utilise ce qui suit pour modifier quelques fichiers dont les chemins Unix ont été écrits dans un fichier :

cat file.txt | while read in; do chmod 755 "$in"; done

Existe-t-il un moyen plus élégant et plus sûr ?

212voto

F. Hauri Points 5893

Lire un fichier ligne par ligne et exécuter des commandes : 4 réponses

C'est parce qu'il n'y a pas qu'une seule réponse...

  1. Expansion de la ligne de commande du shell
  2. xargs outil dédié
  3. while read avec quelques remarques
  4. while read -u en utilisant fd pour interactive traitement (échantillon)

En ce qui concerne la demande de l'OP : en cours d'exécution chmod sur toutes les cibles listées dans le fichier , xargs est l'outil indiqué. Mais pour d'autres applications, de petites quantités de fichiers, etc...

0. Lire le fichier entier comme argument de ligne de commande.

Si votre fichier n'est pas trop gros et que tous les fichiers sont bien nommé (sans espaces ou autres caractères spéciaux comme les guillemets), vous pouvez utiliser l'interpréteur de commandes expansion de la ligne de commande . Tout simplement :

chmod 755 $(<file.txt)

Pour de petites quantités de fichiers (lignes), cette commande est la plus légère.

1. xargs est le bon outil

Pour une plus grande quantité de fichiers, ou presque cualquier nombre de lignes dans votre fichier d'entrée...

Pour beaucoup binutils des outils, comme chown , chmod , rm , cp -t ...

xargs chmod 755 <file.txt

Si vous avez des caractères spéciaux et/ou un grand nombre de lignes dans le fichier file.txt .

xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)

Si votre commande doit être exécutée exactement 1 fois pour chaque entrée :

xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)

Ceci n'est pas nécessaire pour cet échantillon, car chmod accepte plusieurs fichiers comme arguments, mais cela correspond au titre de la question.

Pour certains cas particuliers, vous pourriez même définir l'emplacement de l'argument fichier dans les commandes générées par xargs :

xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)

Test avec seq 1 5 comme entrée

Essayez ça :

xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
Blah 1 blabla 1..
Blah 2 blabla 2..
Blah 3 blabla 3..
Blah 4 blabla 4..
Blah 5 blabla 5..

où votre commande est exécutée une fois par ligne.

2. while read et ses variantes.

Comme le suggère l'OP,

cat file.txt |
while read in; do
    chmod 755 "$in"
done

fonctionnera, mais il y a deux problèmes :

  • cat | es un fourchette inutile et

  • | while ... ;done deviendra un Sous-coque dont l'environnement disparaîtra après ;done .

Donc ça pourrait être mieux écrit :

while read in; do
    chmod 755 "$in"
done < file.txt

Mais

  • Vous pouvez être mis en garde contre $IFS y read drapeaux :

help read

read: read [-r] ... [-d delim] ... [name ...]
    ...
    Reads a single line from the standard input... The line is split
    into fields as with word splitting, and the first word is assigned
    to the first NAME, the second word to the second NAME, and so on...
    Only the characters found in $IFS are recognized as word delimiters.
    ...
    Options:
      ...
      -d delim   continue until the first character of DELIM is read, 
                 rather than newline
      ...
      -r do not allow backslashes to escape any characters
    ...
    Exit Status:
    The return code is zero, unless end-of-file is encountered...

Dans certains cas, vous pouvez avoir besoin d'utiliser

while IFS= read -r in;do
    chmod 755 "$in"
done <file.txt

pour éviter les problèmes avec les noms de fichiers étranges. Et peut-être si vous rencontrez des problèmes avec UTF-8 :

while LANG=C IFS= read -r in ; do
    chmod 755 "$in"
done <file.txt

Si vous utilisez une redirection depuis l'entrée standard for reading file.txt`, votre script ne peut pas lire les autres entrées de manière interactive (vous ne pouvez plus utiliser l'entrée standard pour les autres entrées).

3. while read en utilisant des fd .

Syntaxe : while read ...;done <file.txt redirigera l'entrée standard pour qu'elle provienne de file.txt . Cela signifie que vous ne pourrez pas vous occuper des processus jusqu'à ce qu'ils soient terminés.

Cela vous permettra d'utiliser plus d'une entrée simultanément, vous pourriez fusionner deux fichiers (comme ici : scriptReplay.sh ), ou peut-être :

Vous envisagez de créer un interactive vous devez éviter d'utiliser l'entrée standard et utiliser un autre descripteur de fichier.

Les descripteurs de fichiers constants sont :

  • 0 pour l'entrée standard
  • 1 pour la sortie standard
  • 2 pour l'erreur standard.

3.1 posix coquille premièrement

Vous pouviez les voir en passant :

ls -l /dev/fd/

ou

ls -l /proc/$$/fd/

À partir de là, vous devez choisir des nombres inutilisés entre 0 et 63 (plus, en fait, en fonction de l'âge de la personne). sysctl superuser tool) comme descripteur de fichier.

Pour cette démo, je vais utiliser le descripteur de fichier 7 :

while read <&7 filename; do
    ans=
    while [ -z "$ans" ]; do
        read -p "Process file '$filename' (y/n)? " foo
        [ "$foo" ] && [ -z "${foo#[yn]}" ] && ans=$foo || echo '??'
    done
    if [ "$ans" = "y" ]; then
        echo Yes
        echo "Processing '$filename'."
    else
        echo No
    fi
done 7<file.txt

Si vous voulez lire votre fichier d'entrée en plusieurs étapes différentes, vous devez utiliser :

exec 7<file.txt      # Without spaces between `7` and `<`!
# ls -l /dev/fd/

read <&7 headLine
while read <&7 filename; do
    case "$filename" in
        *'----' ) break ;;  # break loop when line end with four dashes.
    esac
    ....
done

read <&7 lastLine

exec 7<&-            # This will close file descriptor 7.
# ls -l /dev/fd/

3.2 Même sous bash

Sous bash vous pouvez le laisser choisir librement fd pour vous et le stocker dans une variable :
exec {varname}</path/to/input :

while read -ru ${fle} filename;do
    ans=
    while [ -z "$ans" ]; do
        read -rp "Process file '$filename' (y/n)? " -sn 1 foo
        [ "$foo" ] && [ -z "${foo/[yn]}" ] && ans=$foo || echo '??'
    done
    if [ "$ans" = "y" ]; then
        echo Yes
        echo "Processing '$filename'."
    else
        echo No
    fi
done {fle}<file.txt

Ou

exec {fle}<file.txt
# ls -l /dev/fd/
read -ru ${fle} headline

while read -ru ${fle} filename;do
    [[ -n "$filename" ]] && [[ -z ${filename//*----} ]] && break
    ....
done

read -ru ${fle} lastLine

exec {fle}<&-
# ls -l /dev/fd/

182voto

KingsIndian Points 26855

Oui.

while read in; do chmod 755 "$in"; done < file.txt

De cette façon, vous pouvez éviter un cat processus.

cat est presque toujours mauvais pour un objectif tel que celui-ci. Vous pouvez en savoir plus sur Utilisation inutile du chat.

22voto

d.raev Points 1382

Si vous avez un beau sélecteur (par exemple tous les fichiers .txt dans un répertoire) vous pourriez le faire :

for i in *.txt; do chmod 755 "$i"; done

bash for loop

ou une variante de la vôtre :

while read line; do chmod 755 "$line"; done < file.txt

17voto

janisz Points 1314

Si vous voulez exécuter votre commande en parallèle pour chaque ligne, vous pouvez utiliser GNU Parallel

parallel -a <your file> <program>

Chaque ligne de votre fichier sera passée au programme comme un argument. Par défaut parallel exécute autant de threads que votre nombre de CPUs. Mais vous pouvez le spécifier avec -j

16voto

glenn jackman Points 69748

Si vous savez que vous n'avez pas d'espace dans l'entrée :

xargs chmod 755 < file.txt

S'il peut y avoir des espaces dans les chemins, et si vous avez GNU xargs :

tr '\n' '\0' < file.txt | xargs -0 chmod 755

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X