Como passar cada linha de um arquivo de texto como argumento para um comando?

42

Eu estou olhando para escrever um script que leva um .txtnome de arquivo como argumento, lê o arquivo linha por linha e passa cada linha para um comando. Por exemplo, ele é executado command --option "LINE 1", command --option "LINE 2"etc. A saída do comando é gravada em outro arquivo. Como faço para fazer isso? Não sei por onde começar.

WojciechF
fonte

Respostas:

26

Usar while readloop:

: > another_file  ## Truncate file.

while IFS= read -r LINE; do
    command --option "$LINE" >> another_file
done < file

Outra é redirecionar a saída por bloco:

while IFS= read -r LINE; do
    command --option "$LINE"
done < file > another_file

Última é abrir o arquivo:

exec 4> another_file

while IFS= read -r LINE; do
    command --option "$LINE" >&4
    echo xyz  ## Another optional command that sends output to stdout.
done < file

Se um dos comandos ler entrada, seria uma boa ideia usar outro fd para entrada, para que os comandos não o comam (aqui assumindo ksh, zshou bashpara -u 3, use em <&3vez disso portably):

while IFS= read -ru 3 LINE; do
    ...
done 3< file

Finalmente, para aceitar argumentos, você pode:

#!/bin/bash

FILE=$1
ANOTHER_FILE=$2

exec 4> "$ANOTHER_FILE"

while IFS= read -ru 3 LINE; do
    command --option "$LINE" >&4
done 3< "$FILE"

Qual deles poderia ser executado como:

bash script.sh file another_file

Ideia extra. Com bash, use readarray:

readarray -t LINES < "$FILE"

for LINE in "${LINES[@]}"; do
    ...
done

Nota: IFS=pode ser omitido se você não se importar de ter valores de linha aparados nos espaços à esquerda e à direita.

konsolebox
fonte
28

Outra opção é xargs.

Com o GNU xargs:

< file xargs -I{} -d'\n' command --option {} other args

{} é o espaço reservado para a linha de texto.

Outros xargsnão têm -d, mas alguns têm -0para entrada delimitada por NUL. Com esses, você pode fazer:

< file tr '\n' '\0' | xargs -0 -I{} command --option {} other args

Em sistemas compatíveis com Unix ( -Ié opcional no POSIX e requerido apenas para sistemas compatíveis com Unix), você precisa pré-processar a entrada para citar as linhas no formato esperado por xargs:

< file sed 's/"/"\\""/g;s/.*/"&"/' |
  xargs -E '' -I{} command --option {} other args

No entanto, observe que algumas xargsimplementações têm um limite muito baixo no tamanho máximo do argumento (255 no Solaris, por exemplo, o mínimo permitido pela especificação Unix).

ctrl-alt-delor
fonte
redutível para<file xargs -L 1 -I{} command --option {} other args
iruvar 12/08
@iruvar, não, isso processaria aspas e removeria alguns espaços em branco.
Stéphane Chazelas
7

Mantendo exatamente a pergunta:

#!/bin/bash

# xargs -n param sets how many lines from the input to send to the command

# Call command once per line
[[ -f $1 ]] && cat $1 | xargs -n1 command --option

# Call command with 2 lines as args, such as an openvpn password file
# [[ -f $1 ]] && cat $1 | xargs -n2 command --option

# Call command with all lines as args
# [[ -f $1 ]] && cat $1 | xargs command --option
miigotu
fonte
1
funcionou como um encanto :-)
BugHunter 11/11/18
3

A melhor resposta que encontrei é:

for i in `cat`; do "$cmd" "$i"; done < $file

EDITAR:

... quatro anos depois ...

depois de vários votos negativos e um pouco mais de experiência, recomendo agora o seguinte

xargs -l COMMAND < file
Steffen Roller
fonte
3
Isso chamará o comando uma vez para cada palavra no arquivo. Além disso, você sempre deve citar referências a variáveis ​​do shell (como em do "$cmd" "$i";), a menos que tenha um motivo para não; se o arquivo contivesse uma *como uma palavra por si só, seu código seria executado $cmd *, o que, é claro, executaria o comando com uma lista dos arquivos no diretório atual.
G-Man diz 'Reinstate Monica'
1
@ G-Man, exceto em zsh, o `cat`já expandiria o *(o não citado $iainda poderia expandir alguns caracteres curinga (uma segunda rodada) se a expansão de `cat`introduzir alguns). De qualquer forma, essa abordagem está realmente errada.
Stéphane Chazelas 18/07/2016
1
+1. Isso funcionará bem, se cada linha contiver apenas a entrada necessária para o comando ser usado sem espaços.
Subin Sebastian
Isso age em cada palavra, existe uma variante disso que atua em cada linha?
precisa saber é o seguinte
A questão era processar uma lista de nomes de arquivos linha por linha.
Steffen rolo
2
    sed "s/'/'\\\\''/g;s/.*/\$* '&'/" <<\FILE |\
    sh -s -- command echo --option
all of the{&}se li$n\es 'are safely shell
quoted and handed to command as its last argument
following --option, and, here, before that echo
FILE

RESULTADO

--option all of the{&}se li$n\es 'are safely shell
--option quoted and handed to command as its last argument
--option following --option, and, here, before that echo
mikeserv
fonte
-2
ed file.txt
%g/^/s// /
2,$g/^/-,.j
1s/^/command/
wq
chmod 755 file.txt
./file.txt

Pegue todas as linhas de um arquivo e passe-as como argumentos para um único comando, ou seja,

command line1 line2 line3 ....

Se você precisar que o --optionsinalizador preceda cada linha, altere o segundo comando para:

%g/^/s// --option /
user2217522
fonte
1
O que -1 para usar ed ... realmente?
user2217522
3
Não diminuí o voto, mas a pessoa que provavelmente teve os seguintes motivos: (1) Isso não faz o que a pergunta pede. Isso chama o comando uma vez com todo o conteúdo do arquivo na linha de comando; em vez de uma vez por linha . (2) Isso não faz nada para lidar com caracteres especiais para o shell (que podem estar no arquivo); por exemplo, ', ", <, >, ;, etc (3) Esta cria um ficheiro temporário desnecessária. (4) Geralmente, coisas assim são feitas com "documentos aqui". (5) Seus edcomandos são desajeitados; os dois primeiros comandos podem ser reduzidos para %s/^/ /e %j.
G-Man diz 'Reinstate Monica'