Come posso usare le righe di un file come argomenti di un comando?


159

Dì, ho un file che foo.txtspecifica gli Nargomenti

arg1
arg2
...
argN

che devo passare al comando my_command

Come posso usare le righe di un file come argomenti di un comando?


10
"argomenti", plurale o "argomento", singolo? La risposta accettata è corretta solo nel caso a argomento singolo - che è ciò di cui il corpo del testo indaga - ma non nel caso a argomento multiplo, su cui sembra indagare l'argomento.
Charles Duffy,

Le 4 risposte di seguito di solito hanno risultati identici, tuttavia hanno una semantica leggermente diversa. Ora ricordo perché ho smesso di scrivere script bash: P
Navin

Risposte:


237

Se la tua shell è bash (tra gli altri), una scorciatoia $(cat afile)è $(< afile), quindi dovresti scrivere:

mycommand "$(< file.txt)"

Documentato nella pagina man di bash nella sezione 'Sostituzione comandi'.

In alternativa, fai leggere il tuo comando da stdin, quindi: mycommand < file.txt


11
Essere pedanti, non è una scorciatoia per usare l' <operatore. Significa che la shell stessa eseguirà il reindirizzamento anziché eseguire il catbinario per le sue proprietà di reindirizzamento.
lstyls,

2
È un'impostazione del kernel, quindi dovresti ricompilare il kernel. Oppure esamina il comando xargs
glenn jackman,

3
@ykaner perché senza "$(…)", il contenuto di file.txtverrebbe passato all'input standard di mycommand, non come argomento. "$(…)"significa eseguire il comando e restituire l'output come stringa ; qui il "comando" legge solo il file ma potrebbe essere più complesso.
törzsmókus,

3
Non funziona per me se non perdo le virgolette. Con le virgolette, prende l'intero file come 1 argomento. Senza virgolette, interpreta ogni riga come un argomento separato.
Pete,

1
@LarsH hai assolutamente ragione. Questo è un modo meno confuso per dirlo, grazie.
lstyls,

37

Come già accennato, è possibile utilizzare i backtick o $(cat filename).

Ciò che non è stato menzionato, e penso sia importante notare, è che devi ricordare che la shell spezzerà il contenuto di quel file in base allo spazio bianco, dando ogni "parola" che trova al tuo comando come argomento. E mentre potresti essere in grado di racchiudere un argomento della riga di comando tra virgolette in modo che possa contenere spazi bianchi, sequenze di escape, ecc., La lettura dal file non farà la stessa cosa. Ad esempio, se il tuo file contiene:

a "b c" d

gli argomenti che otterrai saranno:

a
"b
c"
d

Se vuoi tirare ogni riga come argomento, usa il costrutto while / read / do:

while read i ; do command_name $i ; done < filename

Avrei dovuto menzionarlo, presumo che tu stia usando bash. Mi rendo conto che ci sono altre shell là fuori, ma quasi tutte le macchine * nix su cui ho lavorato hanno funzionato bash o equivalenti. IIRC, questa sintassi dovrebbe funzionare allo stesso modo su ksh e zsh.
Sarà il

1
La lettura dovrebbe essere a read -rmeno che non si desideri espandere le sequenze backslash-escape - e NUL è un delimitatore più sicuro da utilizzare rispetto alla nuova riga, in particolare se gli argomenti che si stanno passando sono cose come i nomi di file, che possono contenere letterali letterali. Inoltre, senza cancellare IFS, si ottiene uno spazio bianco iniziale e finale implicitamente eliminato i.
Charles Duffy,

18
command `< file`

passerà il contenuto del file al comando su stdin, ma rimuoverà le nuove righe, il che significa che non è possibile scorrere su ciascuna riga singolarmente. Per questo potresti scrivere uno script con un ciclo 'for':

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

Oppure (la variante multilinea):

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

Oppure (variante multilinea con $()invece di ``):

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

Riferimenti:

  1. Per la sintassi del loop: https://www.cyberciti.biz/faq/bash-for-loop/

Inoltre, inserendo i < filebacktick significa che in realtà non esegue affatto un reindirizzamento command.
Charles Duffy,

3
(Forse le persone che upvoted questo in realtà testarlo? command `< file` Non funziona sia in bash o POSIX sh; zsh è una questione diversa, ma non è la shell a questa domanda è di circa).
Charles Duffy,

@CharlesDuffy Puoi essere più specifico su come non funziona?
mwfearnley,

@CharlesDuffy Funziona con bash 3.2 e zsh 5.3. Questo non funzionerà in sh, ash e dash, ma nemmeno $ (<file).
Arnie97,

1
@mwfearnley, felice di fornire questa specificità. L'obiettivo è quello di ripetere le righe, giusto? Metti Hello Worldsu una riga. Lo vedrai prima eseguito some_command Hello, quindi some_command World, no some_command "Hello World" o some_command Hello World.
Charles Duffy,

15

Lo fai usando i backtick:

echo World > file.txt
echo Hello `cat file.txt`

4
Questo non crea un argomento - poiché non è citato, è soggetto a suddivisione in stringhe, quindi se si emettesse echo "Hello * Starry * World" > file.txtnel primo passaggio, si otterrebbero almeno quattro argomenti separati passati al secondo comando - e probabilmente di più, poiché la *s si espanderebbe ai nomi dei file presenti nella directory corrente.
Charles Duffy,

3
... e poiché sta eseguendo l'espansione glob, non emette esattamente cosa c'è nel file. E poiché sta eseguendo un'operazione di sostituzione dei comandi, è estremamente inefficiente - in realtà sta fork()inghiottendo una subshell con un FIFO collegato al suo stdout, quindi invocando /bin/catcome figlio di quella subshell, quindi leggendo l'output tramite FIFO; confronta con $(<file.txt), che legge il contenuto del file in bash direttamente senza sottotitoli o FIFO coinvolti.
Charles Duffy,

11

Se vuoi farlo in un modo robusto che funzioni per ogni possibile argomento della riga di comando (valori con spazi, valori con nuove righe, valori con caratteri di virgolette letterali, valori non stampabili, valori con caratteri glob, ecc.), Diventa un po ' più interessante.


Per scrivere su un file, dato un array di argomenti:

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

... sostituirlo con "argument one", "argument two"ecc a seconda dei casi.


Per leggere da quel file e usare il suo contenuto (in bash, ksh93 o un'altra shell recente con array):

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

Per leggere da quel file e usare il suo contenuto (in una shell senza array; nota che questo sovrascriverà il tuo elenco di argomenti della riga di comando locale, e quindi è meglio farlo all'interno di una funzione, in modo da sovrascrivere gli argomenti della funzione e non l'elenco globale):

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

Si noti che -d(consentendo l'utilizzo di un diverso delimitatore di fine riga) è un'estensione non POSIX e una shell senza array potrebbe non supportarla. In tal caso, potrebbe essere necessario utilizzare un linguaggio non shell per trasformare il contenuto delimitato da NUL in un evalformato sicuro:

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 "$@"

6

Ecco come passo i contenuti di un file come argomento a un comando:

./foo --bar "$(cat ./bar.txt)"

1
Questo è - ma per essere sostanzialmente meno efficiente - effettivamente equivalente a $(<bar.txt)(quando si usa una shell, come bash, con l'estensione appropriata). $(<foo)è un caso speciale: a differenza della normale sostituzione dei comandi, come usato in $(cat ...), non esegue il fork di una subshell in cui operare, evitando così un notevole sovraccarico.
Charles Duffy,

3

Se tutto ciò che devi fare è girare il file arguments.txtcon i contenuti

arg1
arg2
argN

in my_command arg1 arg2 argNallora puoi semplicemente usare xargs:

xargs -a arguments.txt my_command

È possibile inserire ulteriori argomenti statici nella xargschiamata, ad esempio xargs -a arguments.txt my_command staticArgquale chiameràmy_command staticArg arg1 arg2 argN


1

Nella mia shell bash il seguente ha funzionato come un incantesimo:

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

dove input_file è

arg1
arg2
arg3

Come evidente, questo ti consente di eseguire più comandi con ciascuna riga da input_file, un piccolo trucco che ho imparato qui .


3
Il tuo "simpatico trucchetto" è pericoloso dal punto di vista della sicurezza: se hai un argomento contenente $(somecommand), riceverai quel comando piuttosto che passarlo come testo. Allo stesso modo, >/etc/passwdverrà elaborato come reindirizzamento e sovrascrivere /etc/passwd(se eseguito con le autorizzazioni appropriate), ecc.
Charles Duffy,

2
È invece molto più sicuro eseguire le seguenti operazioni (su un sistema con estensioni GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- e anche più efficiente, poiché trasmette quanti più argomenti possibile a ciascuna shell anziché avviare una shell per riga nel file di input.
Charles Duffy,

E, naturalmente, perdi l' uso inutile dicat
tripleee

0

Dopo aver modificato la risposta di @Wesley Rice un paio di volte, ho deciso che le mie modifiche stavano diventando troppo grandi per continuare a cambiare la sua risposta invece di scrivere la mia. Quindi, ho deciso di scrivere il mio!

Leggi ogni riga di un file e operaci riga per riga in questo modo:

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

Questo viene direttamente dall'autore Vivek Gite qui: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Ottiene il merito!

Sintassi: leggi il file riga per riga su una shell Unix e Linux di Bash:
1. La sintassi è la seguente per bash, ksh, zsh e tutte le altre shell per leggere un file riga per riga
2. while read -r line; do COMMAND; done < input.file
3. L' -ropzione è passata al comando read impedisce l'interpretazione delle escape di backslash.
4. Aggiungere IFS=un'opzione prima del comando di lettura per evitare che gli spazi bianchi iniziali / finali vengano tagliati -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


E ora per rispondere a questa domanda ora chiusa che ho anche avuto: è possibile `git aggiungere` un elenco di file da un file? - ecco la mia risposta:

Si noti che FILES_STAGEDè una variabile che contiene il percorso assoluto di un file che contiene un gruppo di righe in cui ogni riga è un percorso relativo di un file su cui mi piacerebbe fare git add. Questo frammento di codice sta per diventare parte del file "eRCaGuy_dotfiles / util_scripts / sync_git_repo_to_build_machine.sh" in questo progetto, per consentire una facile sincronizzazione dei file in sviluppo da un PC (es: un computer su cui accendo ) ad un altro (es: a computer più potente su cui costruisco): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

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

Riferimenti:

  1. Da dove ho copiato la mia risposta da: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Per la sintassi del loop: https://www.cyberciti.biz/faq/bash-for-loop/

Relazionato:

  1. Come leggere il contenuto del file riga per riga e fare git addsu di esso: è possibile `git aggiungere` un elenco di file da un file?
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.