Executando um comando várias vezes com argumentos (nomes de arquivos) de um arquivo?

3

Eu tenho um arquivo com uma longa lista de nomes de arquivos (com caminhos completos). Eu também tenho um programa que gostaria de executar várias vezes, usando um e um nome de arquivo da lista como argumento. O programa que gostaria de executar é, infelizmente, um roteiro feito por você mesmo; então ele só pode levar um nome de arquivo por vez, e não pode aceitar entrada de stdin (como uma lista de nomes de arquivos) - então não há redirecionamento de tubulação ou entrada possível. / p>

O que eu preciso, é um comando que irá executar o outro comando (meu script), usando as linhas no arquivo como argumento.

Algo parecido com find com o -exec -action ... ( find . -name "*.txt" -exec command \; ). Eu acho que na verdade poderia usar find aqui, mas eu gostaria que os arquivos de entrada fossem classificados ...

Então, o que eu preciso é algo assim:

for_each_command1 -f list_of files -c './my_command {}'
for_each_command2 -f list_of_files -exec ./my_command {} \;
for_each_command3 -c './my_command {}' 

My usual way to handle such tasks is with sed - unfortunately, there's a lot of overhead and it's not pretty (but it does work...):

$ wc -l list_of_files 232 $ for (( i=1; i do > ./my_command "'sed -n "${i}p" list_of_files'" > done

Então, há um comando ou shell embutido para lidar com algo assim?

    
por Baard Kopperud 10.01.2017 / 00:04

7 respostas

4

Convenientemente, xargs -I faz exatamente o que você quer:

$ xargs <my_file_list.txt -I filename ./my_command "filename"
-I replace-str
    Replace occurrences of replace-str in the initial-arguments with names read
    from standard input.
    Also, unquoted blanks do not terminate input items;
    instead the separator is the newline character.
    Implies -x and -L 1.

Isso significa que leva exatamente uma linha de entrada delimitada por nova linha e invoca seu comando como um único argumento. Nada além da nova linha é tratado como um delimitador, portanto, espaços e outros caracteres especiais são bons.

Note que se você quiser permitir novas linhas nos seus nomes de arquivos, o terminador nul (como por resposta do Wildcard) também funcionará em um arquivo.

    
por 10.01.2017 / 12:26
3

Eu geralmente prefiro soluções portáteis (POSIX), mas a resposta simples e óbvia aqui é usar as extensões do GNU para facilitar sua vida:

find . -type f -name '*.txt' -print0 | sort -z | xargs -n1 -r0 ./mycommand

Isso usa bytes nulos como separadores entre nomes de arquivos, o que é importante, pois os nomes de arquivos podem conter qualquer , incluindo novas linhas.

(As barras também são reservadas, pois são separadores de componentes de caminho, mas isso não importa neste contexto porque você não pode usá-los como delimitadores de arquivos.)

    
por 10.01.2017 / 10:21
2

Assumo que sua lista de arquivos está armazenada em um arquivo chamado 'filelist.txt', cada nome de arquivo em uma linha. Então você pode chamar seu script com cada linha como um argumento como segue:

while read name; do
  ./yoursrcipt.sh "$name"
done <filelist.txt
    
por 10.01.2017 / 00:17
1
sed < list_of_files 's,^,./mycommand ",;s,$,",' | sh
    
por 10.01.2017 / 02:20
1
$ cat baard.in
/path/to/some space file
/some   tabbed/some     tabbed  file
/path to/genericfile
/path/to/regular.file
/path/to/exciting!/file!
/path/to/(parenthetical)/file.txt

$ cat my_command
#!/bin/sh
printf "my_command: %s\n" "$1"

Como o awk já analisa linhas, você poderia usá-lo para chamar seu comando com cada registro, tomando o cuidado de citar o parâmetro:

$ awk '{system("./my_command '\''" $0 "'\''");}' < baard.in
my_command: /path/to/some space file
my_command: /some       tabbed/some     tabbed  file
my_command: /path to/genericfile
my_command: /path/to/regular.file
my_command: /path/to/exciting!/file!
my_command: /path/to/(parenthetical)/file.txt

O comando awk falha se houver aspas simples na entrada, como, por exemplo:

/books/Hitchhiker's Guide to the Galaxy.epub

Se você tiver o bash disponível, poderá usar:

$ readarray -t paths < baard.in
$ for((i=0;i < ${#paths[*]}; i++)); do ./my_command "${paths[i]}"; done
my_command: /path/to/some space file
my_command: /some       tabbed/some     tabbed  file
my_command: /path to/genericfile
my_command: /path/to/regular.file
my_command: /path/to/exciting!/file!
my_command: /path/to/(parenthetical)/file.txt
my_command: /books/Hitchhiker's Guide to the Galaxy.epub

ou em ksh93 (gorjeta para resposta de Gilles aqui para a implementação manual do readarray):

$ IFS=$'\n'; set -f
$ paths=( $(< baard.in) )
$ unset IFS; set +f
$ for((i=0;i < ${#paths[*]}; i++)) do ./my_command "${paths[i]}"; done
my_command: /path/to/some space file
my_command: /some       tabbed/some     tabbed  file
my_command: /path to/genericfile
my_command: /path/to/regular.file
my_command: /path/to/exciting!/file!
my_command: /path/to/(parenthetical)/file.txt
my_command: /books/Hitchhiker's Guide to the Galaxy.epub
    
por 10.01.2017 / 03:15
0

Se eu entendi sua pergunta corretamente, algo assim deve fazer o que você precisa (supondo que seu arquivo tenha um nome de arquivo por linha):

for i in 'cat list_of_files'; do ./mycommand "$i"; done
    
por 10.01.2017 / 00:21
0

Pelo que entendi, você perguntou como ler um arquivo e executar uma tarefa para cada linha lida.

while IFS= read -r line
do    mycmd "$line"
done  < infile

ou se você quiser classificar sua entrada primeiro:

sort ./infile[123] |
while IFS= read -r line
do    mycmd "$line"
done
    
por 18.01.2017 / 11:56