Comment utiliser les lignes d'un fichier comme arguments d'une commande?

159

Dis, j'ai un fichier foo.txtspécifiant des Narguments

arg1
arg2
...
argN

que je dois passer à la commande my_command

Comment utiliser les lignes d'un fichier comme arguments d'une commande?

Yoo
la source
10
"arguments", pluriel ou "argument", unique? La réponse acceptée n'est correcte que dans le cas de l'argument unique - qui est ce sur quoi le corps du texte interroge - mais pas dans le cas de l'argument multiple, sur lequel le sujet semble enquêter.
Charles Duffy
Les 4 réponses ci-dessous ont généralement des résultats identiques, mais leur sémantique est légèrement différente. Maintenant, je me souviens pourquoi j'ai arrêté d'écrire des scripts bash: P
Navin

Réponses:

237

Si votre shell est bash (entre autres), un raccourci pour $(cat afile)est $(< afile), donc vous écrirez:

mycommand "$(< file.txt)"

Documenté dans la page de manuel bash dans la section 'Command Substitution'.

Alternativement, faites lire votre commande à partir de stdin, donc: mycommand < file.txt

Glenn Jackman
la source
11
Pour être pédant, ce n'est pas un raccourci pour utiliser l' <opérateur. Cela signifie que le shell lui-même effectuera la redirection plutôt que d'exécuter le catbinaire pour ses propriétés de redirection.
lstyls
2
C'est un paramètre du noyau, vous devez donc recompiler le noyau. Ou enquêter sur la commande xargs
Glenn Jackman
3
@ykaner car sans "$(…)", le contenu de file.txtserait passé à l'entrée standard de mycommand, pas en tant qu'argument. "$(…)"signifie exécuter la commande et restituer la sortie sous forme de chaîne ; ici la «commande» ne lit que le fichier mais cela pourrait être plus complexe.
törzsmókus
3
Cela ne fonctionne pas pour moi à moins que je perde les citations. Avec les guillemets, il prend le fichier entier comme 1 argument. Sans guillemets, il interprète chaque ligne comme un argument distinct.
Pete
1
@LarsH vous avez absolument raison. C'est une façon moins déroutante de le dire, merci.
lstyls
37

Comme déjà mentionné, vous pouvez utiliser les backticks ou $(cat filename).

Ce qui n'a pas été mentionné, et je pense qu'il est important de le noter, c'est que vous devez vous rappeler que le shell divisera le contenu de ce fichier selon des espaces, en donnant chaque "mot" qu'il trouve à votre commande comme argument. Et bien que vous puissiez mettre un argument de ligne de commande entre guillemets afin qu'il puisse contenir des espaces, des séquences d'échappement, etc., la lecture à partir du fichier ne fera pas la même chose. Par exemple, si votre fichier contient:

a "b c" d

les arguments que vous obtiendrez sont:

a
"b
c"
d

Si vous souhaitez extraire chaque ligne comme argument, utilisez la construction while / read / do:

while read i ; do command_name $i ; done < filename
Volonté
la source
J'aurais dû le mentionner, je suppose que vous utilisez bash. Je me rends compte qu'il y a d'autres shells là-bas, mais presque toutes les machines * nix sur lesquelles j'ai travaillé ont fonctionné sous forme de bash ou d'un équivalent. IIRC, cette syntaxe devrait fonctionner de la même manière sur ksh et zsh.
Volonté
1
Read devrait l'être à read -rmoins que vous ne souhaitiez étendre les séquences d'échappement anti-slash - et NUL est un délimiteur plus sûr à utiliser que le saut de ligne, en particulier si les arguments que vous passez sont des choses comme des noms de fichiers, qui peuvent contenir des sauts de ligne littéraux. De plus, sans effacer IFS, les espaces de début et de fin sont supprimés implicitement i.
Charles Duffy
18
command `< file`

transmettra le contenu du fichier à la commande sur stdin, mais supprimera les nouvelles lignes, ce qui signifie que vous ne pouvez pas parcourir chaque ligne individuellement. Pour cela, vous pouvez écrire un script avec une boucle 'for':

for line in `cat input_file`; do some_command "$line"; done

Ou (la variante multiligne):

for line in `cat input_file`
do
    some_command "$line"
done

Ou (variante multiligne avec $()au lieu de ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Références:

  1. Pour la syntaxe de la boucle: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
la source
De plus, mettre des < filebackticks signifie qu'il n'effectue pas du tout de redirection command.
Charles Duffy
3
(Est-ce que les personnes qui ont voté pour cela l'ont réellement testé? command `< file` Ne fonctionne ni dans bash ni dans POSIX sh; zsh est une autre affaire, mais ce n'est pas le shell sur lequel porte cette question).
Charles Duffy
@CharlesDuffy Pouvez-vous être plus précis sur la façon dont cela ne fonctionne pas?
mwfearnley
@CharlesDuffy Cela fonctionne dans bash 3.2 et zsh 5.3. Cela ne fonctionnera pas dans sh, ash et dash, mais ni dans $ (<file).
Arnie97
1
@mwfearnley, heureux de fournir cette spécificité. Le but est d'itérer sur les lignes, non? Mettez Hello Worldsur une ligne. Vous le verrez d'abord s'exécuter some_command Hello, alors some_command World, pas some_command "Hello World" ou some_command Hello World.
Charles Duffy
15

Vous faites cela en utilisant des backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
la source
4
Cela ne crée pas d' argument - car il n'est pas entre guillemets, il est sujet au fractionnement de chaîne, donc si vous émettez echo "Hello * Starry * World" > file.txtdans la première étape, vous obtiendrez au moins quatre arguments séparés passés à la deuxième commande - et probablement plus, car le *s s'étendrait aux noms des fichiers présents dans le répertoire courant.
Charles Duffy
3
... et comme il exécute une expansion globale, il n'émet pas exactement ce qu'il y a dans le fichier. Et comme il effectue une opération de substitution de commande, il est extrêmement inefficace - il est en fait fork()en dehors d'un sous-shell avec un FIFO attaché à son stdout, puis en l'appelant en /bin/cattant qu'enfant de ce sous-shell, puis en lisant la sortie via le FIFO; compare to $(<file.txt), qui lit le contenu du fichier dans bash directement sans sous-shell ou FIFO impliqué.
Charles Duffy
11

Si vous souhaitez le faire d'une manière robuste qui fonctionne pour tous les arguments de ligne de commande possibles (valeurs avec espaces, valeurs avec sauts de ligne, valeurs avec guillemets littéraux, valeurs non imprimables, valeurs avec caractères glob, etc.), il obtient un peu plus intéressant.


Pour écrire dans un fichier, étant donné un tableau d'arguments:

printf '%s\0' "${arguments[@]}" >file

... remplacer par "argument one", "argument two", etc. , selon le cas.


Pour lire ce fichier et utiliser son contenu (dans bash, ksh93 ou dans un autre shell récent avec des tableaux):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Pour lire à partir de ce fichier et utiliser son contenu (dans un shell sans tableaux; notez que cela écrasera votre liste d'arguments de ligne de commande locale, et qu'il est donc préférable de le faire à l'intérieur d'une fonction, de sorte que vous écrasiez les arguments de la fonction et non la liste globale):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Notez que -d(autoriser l'utilisation d'un délimiteur de fin de ligne différent) est une extension non POSIX, et un shell sans tableaux peut également ne pas la prendre en charge. Si tel est le cas, vous devrez peut-être utiliser un langage non-shell pour transformer le contenu délimité par NUL en une evalforme -safe:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"
Charles Duffy
la source
6

Voici comment je passe le contenu d'un fichier comme argument à une commande:

./foo --bar "$(cat ./bar.txt)"
chovy
la source
1
Ceci est - mais pour être nettement moins efficace - effectivement équivalent à $(<bar.txt)(lors de l'utilisation d'un shell, tel que bash, avec l'extension appropriée). $(<foo)est un cas particulier: contrairement à la substitution de commandes régulière, telle qu'utilisée dans $(cat ...), elle ne crée pas un sous-shell pour fonctionner, et évite ainsi une surcharge importante.
Charles Duffy
3

Si tout ce que vous avez à faire est de tourner le fichier arguments.txtavec le contenu

arg1
arg2
argN

en my_command arg1 arg2 argNvous pouvez simplement utiliser xargs:

xargs -a arguments.txt my_command

Vous pouvez mettre des arguments statiques supplémentaires dans l' xargsappel, comme celui xargs -a arguments.txt my_command staticArgqui appelleramy_command staticArg arg1 arg2 argN

Cobra_Fast
la source
1

Dans ma coquille bash, ce qui suit a fonctionné comme un charme:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

où input_file est

arg1
arg2
arg3

Comme évident, cela vous permet d'exécuter plusieurs commandes avec chaque ligne de input_file, une jolie petite astuce que j'ai apprise ici .

honkaboy
la source
3
Votre "petite astuce" est dangereuse du point de vue de la sécurité - si vous avez un argument contenant $(somecommand), vous obtiendrez cette commande exécutée plutôt que transmise sous forme de texte. De même, >/etc/passwdsera traité comme une redirection et un écrasement /etc/passwd(si exécuté avec les autorisations appropriées), etc.
Charles Duffy
2
Il est beaucoup plus sûr de faire ce qui suit à la place (sur un système avec des extensions GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- et aussi plus efficace, car il transmet autant d'arguments à chaque shell que possible plutôt que de démarrer un shell par ligne dans votre fichier d'entrée.
Charles Duffy
Et bien sûr, perdez l' utilisation inutile decat
tripleee
0

Après avoir édité la réponse de @Wesley Rice à quelques reprises, j'ai décidé que mes changements devenaient trop importants pour continuer à changer sa réponse au lieu d'écrire la mienne. Alors, j'ai décidé que je devais écrire le mien!

Lisez chaque ligne d'un fichier et opérez-la ligne par ligne comme ceci:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Cela vient directement de l'auteur Vivek Gite ici: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Il en a le mérite!

Syntaxe: Lire un fichier ligne par ligne sur un shell Bash Unix & Linux:
1. La syntaxe est la suivante pour bash, ksh, zsh et tous les autres shells pour lire un fichier ligne par ligne
2. while read -r line; do COMMAND; done < input.file
3. L' -roption passée à la commande read empêche l'interprétation des échappements antislash.
4. Ajoutez une IFS=option avant la commande de lecture pour éviter que les espaces de début / fin ne soient coupés -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


Et maintenant pour répondre à cette question maintenant fermée que j'avais aussi: est-il possible de `git add` une liste de fichiers à partir d'un fichier? - voici ma réponse:

Notez qu'il FILES_STAGEDs'agit d'une variable contenant le chemin absolu vers un fichier qui contient un tas de lignes où chaque ligne est un chemin relatif vers un fichier sur lequel j'aimerais faire git add. Cet extrait de code est sur le point de faire partie du fichier «eRCaGuy_dotfiles / useful_scripts / sync_git_repo_to_build_machine.sh» de ce projet, pour permettre une synchronisation facile des fichiers en développement d'un PC (ex: un ordinateur sur lequel je code) à un autre (ex: a ordinateur plus puissant sur lequel je construis): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Références:

  1. Où j'ai copié ma réponse à partir de: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Pour la syntaxe de la boucle: https://www.cyberciti.biz/faq/bash-for-loop/

En relation:

  1. Comment lire le contenu d'un fichier ligne par ligne et faire git adddessus: Est-il possible de `git add` une liste de fichiers à partir d'un fichier?
Gabriel Staples
la source