Saída adequada de saída do pipe em xargs

4

Exemplo:

% touch -- safe-name -name-with-dash-prefix "name with space" \
    'name-with-double-quote"' "name-with-single-quote'" \
    'name-with-backslash\'

xargs parece não conseguir lidar com aspas duplas:

% ls | xargs ls -l 
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
ls: invalid option -- 'e'
Try 'ls --help' for more information.

Se usarmos a opção -0 , haverá problemas com o nome que tenha prefixo de traço:

% ls -- * | xargs -0 -- ls -l --
ls: invalid option -- 'e'
Try 'ls --help' for more information.

Isso é antes de usar outros caracteres potencialmente problemáticos, como nova linha, caractere de controle, etc.

    
por Gerry Lufwansa 11.08.2017 / 14:17

4 respostas

5

A especificação POSIX oferece um exemplo disso:

ls | sed -e 's/"/"\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'

(com nomes de arquivos sendo seqüências arbitrárias de bytes (diferente de / e NULL) e sed / xargs esperando texto , você também precisaria para corrigir o código de idioma para C (onde todos os bytes não-NUL criariam caracteres válidos) para torná-lo confiável (exceto para xargs implementações que têm um limite muito baixo no comprimento máximo de um argumento))

O -E '' é necessário para algumas implementações xargs que, sem ele, entenderiam um argumento _ para significar o final da entrada (em que echo a _ b | xargs produz a apenas, por exemplo).

Com o GNU xargs , você pode usar:

ls | xargs -d '\n' printf '<%s>\n'

O GNU xargs também tem um -0 que foi copiado por algumas outras implementações, portanto:

ls | tr '\n' '
a
b
' | xargs -0 printf '<%s>\n'

é um pouco mais portátil.

Todos eles assumem que os nomes dos arquivos não contêm caracteres de nova linha. Se houver nomes de arquivos com caracteres de nova linha, a saída de ls simplesmente não será pós-processável. Se você receber:

eval "files=($(ls --quoting-style=shell-always))"
[ "${#files[#]}" -eq 0 ] || printf '%s
ls | sed -e 's/"/"\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'
' "${files[@]}" | xargs -0 printf '<%s>\n'

Podem ser dois arquivos a e b ou um arquivo chamado a<newline>b , não há como saber.

O GNU ls tem um --quoting-style=shell-always que torna sua saída não ambígua e poderia ser pós-processável, mas a citação não é compatível com a citação esperada por xargs . xargs reconhecer "..." , \x e '...' formas de cotação. Mas tanto "..." como '...' são aspas strongs e não podem conter caracteres de nova linha (somente \ pode escapar de caracteres de nova linha para xargs ), portanto isso não é compatível com aspas onde apenas '...' são aspas strongs ( e pode conter caracteres de nova linha), mas \<newline> é uma continuação de linha (é removida) em vez de uma nova linha de escape.

Você pode usar o shell para analisar essa saída e, em seguida, enviá-la em um formato esperado por xargs :

ls | xargs -d '\n' printf '<%s>\n'
    
por 11.08.2017 / 16:20
3

Para xargs entender a opção de entrada -0 null, a parte de envio também deve aplicar o delimitador nulo aos dados que estão enviando.

Senão não há sincronização entre os dois.

Uma opção é o comando GNU find , que pode colocar esses delimitadores:

find . -maxdepth 1 ! -name . -print0 | xargs -0 ls -ld
    
por 11.08.2017 / 14:50
3

Como você disse, xargs não gosta de aspas duplas sem correspondência, a menos que você use -0 , mas -0 só faz sentido se você alimentar dados com terminação nula. Então, isso falha:

$ echo * | xargs
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
name-with-backslash -name-with-dash-prefix

Mas isso funciona:

$ printf '%s
$ for f in *; do ls -l -- "$f"; done
-rw-r--r-- 1 terdon terdon 4142 Aug 11 16:03 a
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-backslash\'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 -name-with-dash-prefix
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-double-quote"'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 "name-with-single-quote'"
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name with space'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 safe-name
' -- * | xargs -0 -- name-with-backslash\ -name-with-dash-prefix name-with-double-quote" name-with-single-quote' name with space safe-name

Em qualquer caso, sua abordagem básica não é realmente a melhor maneira de fazer isso. Em vez de brincar com xargs e ls e outras coisas, basta usar shell globs:

$ echo * | xargs
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
name-with-backslash -name-with-dash-prefix
    
por 11.08.2017 / 15:07
0

É extremamente bobo tentar analisar a saída de um comando ls que é não foi projetado para ser analisado para alimentar um comando que não foi projetado para lidar com vários caracteres (por exemplo: new lines e {} ) quando o shell faz isso sozinho:

set -- *; for f; do echo "<$f>"; done

set    -- *
for    f
do     ls "$f"
done

Ou, em uma linha de comando:

$ set -- *; for f; do echo "<$f>"; done
<name-with-backslash\>
<-name-with-dash-prefix>
<name-with-double-quote">
<name-with-single-quote'>
<name with space>
<safe-name>
<with_a
newline>

Note que a saída lida (e tem n exemplo como último nome de arquivo) com novas linhas perfeitamente bem.

Ou, se o número de arquivos torna o shell lento, use find:

$ find ./ -type f -exec echo '<{}>' \;
<./safe-name>
<./with_a
newline>
<./name-with-double-quote">
<./-name-with-dash-prefix>
<./name with space>
<./name-with-single-quote'>
<./name-with-backslash\>

Lembre-se de encontrar todos os arquivos-ponto e todos os subdiretórios diferentes do shell.

    
por 14.08.2017 / 04:30