Par exemple, en ce moment, j'utilise ce qui suit pour modifier quelques fichiers dont j'ai écrit les chemins Unix 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?
C'est parce qu'il n'y a pas qu'une seule réponse ...
shell
expansion de la ligne de commandexargs
outil dédiéwhile read
avec quelques remarqueswhile read -u
utilisation dédiée fd
, pour traitement interactif (exemple)Concernant la requête OP: s'exécutant chmod
sur toutes les cibles listées dans le fichier , xargs
est l'outil indiqué. Mais pour certaines autres applications, petite quantité de fichiers, etc ...
Si votre fichier n'est pas trop volumineux et que tous les fichiers sont bien nommés (sans espaces ou autres caractères spéciaux comme les guillemets), vous pouvez utiliser l' shell
extension de ligne de commande . Simplement:
chmod 755 $(<file.txt)
Pour une petite quantité de fichiers (lignes), cette commande est la plus légère.
xargs
est le bon outilPour une plus grande quantité de fichiers, ou presque n'importe quel nombre de lignes dans votre fichier d'entrée ...
Pour de nombreux binutils outils, comme chown
, chmod
, rm
, cp -t
...
xargs chmod 755 <file.txt
Si vous avez des caractères spéciaux et / ou beaucoup de lignes dans file.txt
.
xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
si votre commande doit être exécutée exactement 1 fois par entrée:
xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
Ce n'est pas nécessaire pour cet exemple, car chmod
accepter plusieurs fichiers comme argument, mais cela correspond au titre de la question.
Dans certains cas particuliers, vous pouvez même définir l'emplacement de l'argument de fichier dans les commandes générées par xargs
:
xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)
seq 1 5
comme entréeEssaye ç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ù la commande est effectuée une fois par ligne .
while read
et variantes.Comme OP le suggère, cat file.txt | while read in; do chmod 755 "$in"; done
cela fonctionnera, mais il y a 2 problèmes:
cat |
est une fourchette inutile , et
| while ... ;done
deviendra un sous - shell où l'environnement disparaîtra après ;done
.
Donc, cela pourrait être mieux écrit:
while read in; do chmod 755 "$in"; done < file.txt
Mais,
Vous pouvez être averti $IFS
et read
signale:
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 devrez peut-être 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
Pendant que vous l'utilisez STDIN
pour la lecture file.txt
, votre script ne peut pas être interactif (vous ne pouvez plus l'utiliser STDIN
).
while read -u
, en utilisant dédié fd
.Syntaxe: while read ...;done <file.txt
redirigera STDIN
vers file.txt
. Cela signifie que vous ne pourrez pas gérer le processus tant qu'il ne sera pas terminé.
Si vous envisagez de créer un outil interactif , vous devez éviter d'utiliser STDIN
et utiliser un autre descripteur de fichier .
Les descripteurs de fichiers de constantes sont: 0
pour STDIN , 1
pour STDOUT et 2
pour STDERR . Vous pouvez les voir par:
ls -l /dev/fd/
ou
ls -l /proc/self/fd/
À partir de là, vous devez choisir le nombre inutilisé, entre 0
et 63
(plus, en fait, selon l' sysctl
outil du superutilisateur) comme descripteur de fichier :
Pour cette démo, j'utiliserai fd 7
:
exec 7<file.txt # Without spaces between `7` and `<`!
ls -l /dev/fd/
Ensuite, vous pouvez utiliser read -u 7
cette méthode:
while read -u 7 filename;do
ans=;while [ -z "$ans" ];do
read -p "Process file '$filename' (y/n)? " -sn1 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
done
Pour fermer fd/7
:
exec 7<&- # This will close file descriptor 7.
ls -l /dev/fd/
Nota: Je laisse la version barrée car cette syntaxe pourrait être utile, lors de nombreuses E / S avec processus parallèles:
mkfifo sshfifo
exec 7> >(ssh -t user@host sh >sshfifo)
exec 6<sshfifo
xargs
c'était le cas à l'origine pour répondre à ce type de besoin, certaines fonctionnalités, comme la commande de construction le plus longtemps possible dans l'environnement actuel pour invoquerchmod
le moins possible dans ce cas, la réduction des fourches garantit l'efficacité.while ;do..done <$file
implique d'exécuter 1 fork pour 1 fichier.xargs
pourrait exécuter 1 fork pour mille fichiers ... de manière fiable.cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
tr \\n \\0 <file.txt |xargs -0 [command]
est environ 50% plus rapide que la méthode que vous avez décrite.Oui.
De cette façon, vous pouvez éviter un
cat
processus.cat
est presque toujours mauvais pour un objectif comme celui-ci. Vous pouvez en savoir plus sur l' utilisation inutile de Cat.la source
cat
est une bonne idée, mais dans ce cas, la commande indiquée estxargs
chmod
(c'est-à-dire d'exécuter réellement une commande pour chaque ligne du fichier).read -r
lit une seule ligne à partir de l'entrée standard (read
sans-r
interpréter les contre-obliques, vous ne le voulez pas)."si vous avez un bon sélecteur (par exemple tous les fichiers .txt dans un répertoire), vous pouvez faire:
bash pour la boucle
ou une variante de la vôtre:
la source
Si vous savez que vous n'avez aucun espace dans l'entrée:
S'il peut y avoir des espaces dans les chemins, et si vous avez des xargs GNU:
la source
xargs
est robuste. Cet outil est très ancien et son code est fortement revisité . Son objectif était initialement de construire des lignes en respectant les limitations du shell (64kchar / ligne ou quelque chose comme ça). Maintenant, cet outil pourrait fonctionner avec de très gros fichiers et réduire considérablement le nombre de fork à la commande finale. Voir ma réponse et / ouman xargs
.brew install findutils
), puis invoquer GNU xargs avec à lagxargs
place, par exemplegxargs chmod 755 < file.txt
Si vous souhaitez exécuter votre commande en parallèle pour chaque ligne, vous pouvez utiliser GNU Parallel
Chaque ligne de votre fichier sera passée au programme en tant qu'argument. Par défaut,
parallel
exécute autant de threads que vos processeurs comptent. Mais vous pouvez le spécifier avec-j
la source
Je vois que vous avez tagué bash, mais Perl serait également un bon moyen de le faire:
Vous pouvez également appliquer une expression régulière pour vous assurer que vous obtenez les bons fichiers, par exemple pour ne traiter que les fichiers .txt:
Pour "prévisualiser" ce qui se passe, remplacez simplement les contre-crochets par des guillemets doubles et ajoutez
print
:la source
chmod
fonctionperl -lpe 'chmod 0755, $_' file.txt
- utiliser-l
pour la fonction "auto-chomp"Vous pouvez également utiliser AWK qui peut vous donner plus de flexibilité pour gérer le fichier
si votre fichier a un séparateur de champ comme:
Pour n'obtenir que le premier champ que vous faites
Vous pouvez consulter plus de détails sur la documentation GNU https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple
la source
La logique s'applique à de nombreux autres objectifs. Et comment lire .sh_history de chaque utilisateur depuis / home / filesystem? Et s'il y en avait des milliers?
Voici le script https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh
la source
Je sais qu'il est tard mais quand même
Si, par hasard, vous rencontrez un fichier texte Windows enregistré avec
\r\n
au lieu de\n
, vous pourriez être confus par la sortie si votre commande a sth après la ligne de lecture comme argument. Alors supprimez\r
, par exemple:la source