Como uso as linhas de um arquivo como argumentos de um comando?

159

Digamos, eu tenho um arquivo foo.txtespecificando Nargumentos

arg1
arg2
...
argN

que eu preciso passar para o comando my_command

Como uso as linhas de um arquivo como argumentos de um comando?

Yoo
fonte
10
"argumentos", plural ou "argumento", solteiro? A resposta aceita está correta apenas no caso de argumento único - que é o assunto do texto do corpo - mas não no caso de argumento múltiplo, sobre o qual o tópico parece indagar.
Charles Duffy
As 4 respostas abaixo geralmente têm resultados idênticos, mas semânticas ligeiramente diferentes. Agora me lembro por que parei de escrever scripts bash: P
Navin

Respostas:

237

Se seu shell é bash (entre outros), um atalho para $(cat afile)é $(< afile), então você deve escrever:

mycommand "$(< file.txt)"

Documentado na página de manual do bash na seção 'Substituição de comando'.

Em alternativa, leia seu comando a partir de stdin, portanto: mycommand < file.txt

Glenn Jackman
fonte
11
Para ser pedante, não é um atalho para usar o <operador. Isso significa que o próprio shell executará o redirecionamento em vez de executar o catbinário por suas propriedades de redirecionamento.
Lstyls
2
É uma configuração do kernel, então você precisa recompilar o kernel. Ou verificar o comando xargs
glenn jackman
3
@ykaner porque "$(…)", sem , o conteúdo de file.txtseria passado para a entrada padrão de mycommand, não como argumento. "$(…)"significa executar o comando e devolver a saída como uma string ; aqui o "comando" lê apenas o arquivo, mas poderia ser mais complexo.
törzsmókus 7/02/19
3
Não está funcionando para mim, a menos que eu perca as aspas. Com as aspas, leva o arquivo inteiro como 1 argumento. Sem aspas, interpreta cada linha como um argumento separado.
Pete
1
@ LarsH você está absolutamente correto. Essa é uma maneira menos confusa de dizer, obrigado.
Lstyls
37

Como já mencionado, você pode usar os backticks ou $(cat filename).

O que não foi mencionado, e acho importante notar, é que você deve se lembrar que o shell separará o conteúdo desse arquivo de acordo com o espaço em branco, fornecendo cada "palavra" encontrada ao seu comando como argumento. E embora você possa incluir um argumento da linha de comando entre aspas, para que ele possa conter espaços em branco, seqüências de escape etc., a leitura do arquivo não fará a mesma coisa. Por exemplo, se o seu arquivo contiver:

a "b c" d

os argumentos que você receberá são:

a
"b
c"
d

Se você deseja extrair cada linha como argumento, use a construção while / read / do:

while read i ; do command_name $i ; done < filename
Vai
fonte
Eu deveria ter mencionado, estou assumindo que você está usando o bash. Percebo que existem outros shells por aí, mas quase todas as máquinas * nix em que trabalhei rodaram o bash ou algo equivalente. IIRC, essa sintaxe deve funcionar da mesma maneira no ksh e no zsh.
Will
1
A leitura deve ser, a read -rmenos que você queira expandir as seqüências de escape de barra invertida - e NUL é um delimitador mais seguro para usar do que a nova linha, principalmente se os argumentos que você está passando são coisas como nomes de arquivos, que podem conter novas linhas literais. Além disso, sem limpar o IFS, você obtém o espaço em branco inicial e final implicitamente limpo i.
Charles Duffy
18
command `< file`

passará o conteúdo do arquivo para o comando stdin, mas removerá as novas linhas, o que significa que você não pode repetir cada linha individualmente. Para isso, você pode escrever um script com um loop 'for':

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

Ou (a variante de várias linhas):

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

Ou (variante de várias linhas com em $()vez de ``):

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

Referências:

  1. Para sintaxe do loop: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
fonte
Além disso, colocar < filebackticks significa que ele realmente não executa um redirecionamento command.
Charles Duffy
3
(As pessoas que aprovaram isso realmente o testaram? command `< file` Não funcionam no bash ou no POSIX sh; zsh é uma questão diferente, mas esse não é o shell sobre o qual esta questão se refere).
Charles Duffy
@CharlesDuffy Você pode ser mais específico sobre como isso não funciona?
precisa saber é o seguinte
@CharlesDuffy Isso funciona no bash 3.2 e no zsh 5.3. Isso não funcionará em sh, ash e dash, mas nem em $ (<file).
Arnie97 16/08/19
1
@mwfearnley, prazer em fornecer essa especificidade. O objetivo é iterar sobre as linhas, certo? Coloque Hello Worldem uma linha. Você verá a primeira execução some_command Hello, então some_command World, não some_command "Hello World" ou some_command Hello World.
Charles Duffy
15

Você faz isso usando backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
fonte
4
Isso não cria um argumento - porque não é citado, está sujeito à divisão de cadeias; portanto, se você emitiu echo "Hello * Starry * World" > file.txtna primeira etapa, obteria pelo menos quatro argumentos separados passados ​​para o segundo comando - e provavelmente mais, como os *s se expandem para os nomes dos arquivos presentes no diretório atual.
Charles Duffy
3
... e como está executando a expansão glob, não emite exatamente o que há no arquivo. E como está executando uma operação de substituição de comando, é extremamente ineficiente - na verdade, ele está fork()desabilitando um subshell com um FIFO anexado ao seu stdout, invocando /bin/catcomo filho desse subshell e lendo a saída através do FIFO; compare to $(<file.txt), que lê o conteúdo do arquivo no bash diretamente, sem subshells ou FIFOs envolvidos.
Charles Duffy
11

Se você deseja fazer isso de uma maneira robusta que funcione para todos os argumentos de linha de comando possíveis (valores com espaços, valores com novas linhas, valores com caracteres de aspas literais, valores não imprimíveis, valores com caracteres globais, etc.), fica um pouco mais interessante.


Para gravar em um arquivo, com uma matriz de argumentos:

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

... substituir com "argument one", "argument two"etc., conforme apropriado.


Para ler esse arquivo e usar seu conteúdo (no bash, ksh93 ou em outro shell recente com matrizes):

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

Para ler esse arquivo e usar seu conteúdo (em um shell sem matrizes; observe que isso substituirá sua lista de argumentos da linha de comando local e, portanto, é melhor executá-lo dentro de uma função, de modo que você substitua os argumentos da função e não a lista global):

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

Observe que -d(permitir que um delimitador de final de linha diferente seja usado) é uma extensão não POSIX, e um shell sem matrizes também pode não suportá-lo. Nesse caso, pode ser necessário usar uma linguagem não shell para transformar o conteúdo delimitado por NUL em um evalformato seguro:

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
fonte
6

Aqui está como passo o conteúdo de um arquivo como argumento para um comando:

./foo --bar "$(cat ./bar.txt)"
chovy
fonte
1
Isso é - mas por ser substancialmente menos eficiente - efetivamente equivalente a $(<bar.txt)(ao usar um shell, como o bash, com a extensão apropriada). $(<foo)é um caso especial: diferentemente da substituição regular de comandos, como usada $(cat ...), ela não utiliza um subshell para operar e, assim, evita uma grande sobrecarga.
Charles Duffy
3

Se tudo o que você precisa fazer é transformar o arquivo arguments.txtcom o conteúdo

arg1
arg2
argN

para my_command arg1 arg2 argNentão você pode simplesmente usar xargs:

xargs -a arguments.txt my_command

Você pode colocar argumentos estáticos adicionais na xargschamada, como o xargs -a arguments.txt my_command staticArgque chamarámy_command staticArg arg1 arg2 argN

Cobra_Fast
fonte
1

No meu shell bash, o seguinte funcionou como um encanto:

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

onde input_file é

arg1
arg2
arg3

Como é evidente, isso permite que você execute vários comandos com cada linha de input_file, um pequeno truque que aprendi aqui .

honkaboy
fonte
3
Seu "pequeno truque" é perigoso do ponto de vista da segurança - se você tiver um argumento que contenha $(somecommand), esse comando será executado em vez de ser transmitido como texto. Da mesma forma, >/etc/passwdserá processado como um redirecionamento e substituição /etc/passwd(se executado com permissões apropriadas) etc.
Charles Duffy
2
É muito mais seguro fazer o seguinte (em um sistema com extensões GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- e também mais eficiente, pois passa o maior número possível de argumentos para cada shell possível, em vez de iniciar um shell por linha no seu arquivo de entrada.
Charles Duffy
E, claro, perca o uso inútil decat
triplee
0

Depois de editar a resposta de @Wesley Rice algumas vezes, decidi que minhas alterações estavam ficando grandes demais para continuar alterando sua resposta em vez de escrever a minha. Então, eu decidi que preciso escrever o meu!

Leia cada linha de um arquivo e opere linha por linha assim:

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

Isto vem diretamente do autor Vivek Gite aqui: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Ele recebe o crédito!

Sintaxe: Leia o arquivo linha por linha em um shell Bash Unix e Linux:
1. A sintaxe é a seguinte para bash, ksh, zsh e todos os outros shells para ler um arquivo linha por linha
2. while read -r line; do COMMAND; done < input.file
3. A -ropção passou para o comando read impede que as fugas de barra invertida sejam interpretadas.
4. Adicione a IFS=opção antes do comando de leitura para impedir que os espaços em branco à esquerda / à direita sejam aparados -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


E agora, para responder a essa pergunta agora fechada, que eu também tinha: É possível `git add` uma lista de arquivos de um arquivo?- aqui está a minha resposta:

Observe que FILES_STAGEDé uma variável que contém o caminho absoluto para um arquivo que contém várias linhas em que cada linha é um caminho relativo para um arquivo que eu gostaria de fazer git add. Este trecho de código está prestes a se tornar parte do arquivo "eRCaGuy_dotfiles / useful_scripts / sync_git_repo_to_build_machine.sh" neste projeto, para permitir a sincronização fácil de arquivos em desenvolvimento de um PC (por exemplo: um computador que eu codifique) para outro (por exemplo: computador mais poderoso em que construí): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

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

Referências:

  1. De onde copiei minha resposta: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Para sintaxe do loop: https://www.cyberciti.biz/faq/bash-for-loop/

Palavras-chave:

  1. Como ler o conteúdo do arquivo linha por linha e fazê git add-lo: É possível `git add` uma lista de arquivos de um arquivo?
Gabriel Staples
fonte