Por exemplo, agora estou usando o seguinte para alterar alguns arquivos cujos caminhos Unix escrevi para um arquivo:
cat file.txt | while read in; do chmod 755 "$in"; done
Existe uma maneira mais elegante e segura?
Isso ocorre porque não há apenas 1 resposta ...
shell
expansão da linha de comandoxargs
ferramenta dedicadawhile read
com algumas observaçõeswhile read -u
usando dedicado fd
, para processamento interativo (amostra)Quanto ao pedido OP: correndo chmod
em todos os alvos listados no arquivo , xargs
é a ferramenta indicada. Mas para algumas outras aplicações, pequena quantidade de arquivos, etc ...
Se o seu arquivo não for muito grande e todos os arquivos forem bem nomeados (sem espaços ou outros caracteres especiais, como aspas), você poderá usar a shell
expansão da linha de comando . Simplesmente:
chmod 755 $(<file.txt)
Para pequena quantidade de arquivos (linhas), este comando é o mais leve.
xargs
é a ferramenta certaPara uma quantidade maior de arquivos ou quase qualquer número de linhas no seu arquivo de entrada ...
Para muitos binutils ferramentas, como chown
, chmod
, rm
, cp -t
...
xargs chmod 755 <file.txt
Se você possui caracteres especiais e / ou várias linhas file.txt
.
xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
se seu comando precisar ser executado exatamente 1 vez por entrada:
xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
Isso não é necessário para esta amostra, pois chmod
aceita vários arquivos como argumento, mas corresponde ao título da pergunta.
Para algum caso especial, você pode até definir o local do argumento do arquivo nos comandos gerados por xargs
:
xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)
seq 1 5
como entradaTente o seguinte:
xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
Blah 1 blabla 1..
Blah 2 blabla 2..
Blah 3 blabla 3..
Blah 4 blabla 4..
Blah 5 blabla 5..
Onde commande é feito uma vez por linha .
while read
e variantes.Como o OP sugere, cat file.txt | while read in; do chmod 755 "$in"; done
ele funcionará, mas existem 2 problemas:
cat |
é um garfo inútil e
| while ... ;done
se tornará um subshell onde o ambiente desaparecerá depois ;done
.
Portanto, isso poderia ser melhor escrito:
while read in; do chmod 755 "$in"; done < file.txt
Mas,
Você pode ser avisado sobre $IFS
e read
sinalizadores:
help read
read: read [-r] ... [-d delim] ... [name ...] ... Reads a single line from the standard input... The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on... Only the characters found in $IFS are recognized as word delimiters. ... Options: ... -d delim continue until the first character of DELIM is read, rather than newline ... -r do not allow backslashes to escape any characters ... Exit Status: The return code is zero, unless end-of-file is encountered...
Em alguns casos, pode ser necessário usar
while IFS= read -r in;do chmod 755 "$in";done <file.txt
Para evitar problemas com nomes de arquivos estranhos. E talvez se você encontrar problemas comUTF-8
:
while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
Enquanto você usa STDIN
para ler file.txt
, seu script não pode ser interativo (você não pode STDIN
mais usar ).
while read -u
, usando dedicado fd
.Sintaxe: while read ...;done <file.txt
redirecionaráSTDIN
para file.txt
. Isso significa que você não poderá lidar com o processo até que ele termine.
Se você planeja criar uma ferramenta interativa , evite o uso deSTDIN
e usar algum descritor de arquivo alternativo .
Constantes descritores de arquivos são: 0
para STDIN , 1
para STDOUT e 2
para STDERR . Você poderia vê-los por:
ls -l /dev/fd/
ou
ls -l /proc/self/fd/
A partir daí, você deve escolher um número não utilizado, entre 0
e 63
(mais, de fato, dependendo dasysctl
ferramenta de superusuário) como descritor de arquivo :
Para esta demonstração, usarei o fd 7
:
exec 7<file.txt # Without spaces between `7` and `<`!
ls -l /dev/fd/
Então você pode usar read -u 7
desta maneira:
while read -u 7 filename;do
ans=;while [ -z "$ans" ];do
read -p "Process file '$filename' (y/n)? " -sn1 foo
[ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
done
if [ "$ans" = "y" ] ;then
echo Yes
echo "Processing '$filename'."
else
echo No
fi
done 7<file.txt
done
Para fechar fd/7
:
exec 7<&- # This will close file descriptor 7.
ls -l /dev/fd/
Nota: Eu deixei a versão marcada, porque essa sintaxe pode ser útil, ao fazer muitas E / S com processo paralelo:
mkfifo sshfifo
exec 7> >(ssh -t user@host sh >sshfifo)
exec 6<sshfifo
xargs
foi criado inicialmente para responder a esse tipo de necessidade, alguns recursos, como criar o comando o maior tempo possível no ambiente atual, para invocarchmod
neste caso o menos possível, reduzir os garfos garante a eficiência.while ;do..done <$file
implie executando 1 fork para 1 arquivo.xargs
poderia executar 1 fork para milhares de arquivos ... de maneira confiável.cat file.txt | tr \\n \\0 | xargs -0 -n1 chmod 755
tr \\n \\0 <file.txt |xargs -0 [command]
é cerca de 50% mais rápido que o método que você descreveu.Sim.
Dessa forma, você pode evitar um
cat
processo.cat
quase sempre é ruim para um propósito como esse. Você pode ler mais sobre o uso inútil do gato.fonte
cat
seja uma boa ideia, mas, neste caso, o comando indicado éxargs
chmod
(ou seja, realmente executar um comando para cada linha do arquivo).read -r
lê uma única linha da entrada padrão (read
sem-r
interpretar barras invertidas, você não deseja isso)."se você tiver um bom seletor (por exemplo, todos os arquivos .txt em um diretório), poderá:
bash para loop
ou uma variante sua:
fonte
Se você sabe que não possui nenhum espaço em branco na entrada:
Se houver espaço em branco nos caminhos, e se você tiver GNU xargs:
fonte
xargs
é robusto. Essa ferramenta é muito antiga e seu código é fortemente revisitado . Seu objetivo era inicialmente criar linhas em relação às limitações do shell (64kchar / line ou algo assim). Agora, essa ferramenta pode funcionar com arquivos muito grandes e pode reduzir muito o número de bifurcações para o comando final. Vejo minha resposta e / ouman xargs
.brew install findutils
) e, em seguida, invocar o GNU xargs comgxargs
, por exemplo, por exemplo:gxargs chmod 755 < file.txt
Se você deseja executar seu comando em paralelo para cada linha, você pode usar o GNU Parallel
Cada linha do seu arquivo será passada para o programa como argumento. Por padrão,
parallel
executa tantos threads quanto suas CPUs contam. Mas você pode especificá-lo com-j
fonte
Vejo que você marcou o bash, mas o Perl também seria uma boa maneira de fazer isso:
Você também pode aplicar uma regex para garantir que está obtendo os arquivos corretos, por exemplo, para processar apenas os arquivos .txt:
Para "pré-visualizar" o que está acontecendo, substitua os backticks por aspas duplas e preencha
print
:fonte
chmod
funçãoperl -lpe 'chmod 0755, $_' file.txt
- use-l
para o recurso "auto-chomp"Você também pode usar o AWK, que oferece mais flexibilidade para lidar com o arquivo
se o seu arquivo tiver um separador de campos como:
Para obter apenas o primeiro campo que você faz
Você pode verificar mais detalhes na documentação do GNU https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple
fonte
A lógica se aplica a muitos outros objetivos. E como ler .sh_history de cada usuário em / home / filesystem? E se houver milhares deles?
Aqui está o script https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh
fonte
Eu sei que é tarde, mas ainda
Se por acaso você encontrar o arquivo de texto salvo no Windows em
\r\n
vez de\n
, poderá ficar confuso com a saída se o seu comando sth após a linha de leitura como argumento. Então remova\r
, por exemplo:fonte