Como ignorar comandos xargs se a entrada stdin está vazia?

188

Considere este comando:

ls /mydir/*.txt | xargs chown root

A intenção é alterar os proprietários de todos os arquivos de texto mydirpara root

O problema é que, se não houver .txtarquivos mydir, o xargs lança um erro dizendo que não há um caminho especificado. Este é um exemplo inofensivo porque um erro está sendo gerado, mas em alguns casos, como no script que eu preciso usar aqui, é assumido que um caminho em branco é o diretório atual. Portanto, se eu executar esse comando a partir de /home/tom/então, se não houver resultado para ls /mydir/*.txte todos os arquivos abaixo /home/tom/tiverem seus proprietários alterados para raiz.

Então, como posso xargs ignorar um resultado vazio?

HyderA
fonte
6
Além: Nunca canalize a saída lspara uso programático; consulte mywiki.wooledge.org/ParsingLs
Charles Duffy
Meu caso de uso é git branch --merged | grep -v '^* ' | xargs git branch -dque também falha na entrada vazia
belkka

Respostas:

305

Para o GNU xargs, você pode usar a opção -rou --no-run-if-empty:

--no-run-if-empty
-r
Se a entrada padrão não contiver nenhum espaço em branco, não execute o comando. Normalmente, o comando é executado uma vez, mesmo que não haja entrada. Esta opção é uma extensão GNU.

Sven Marnach
fonte
9
Eu honestamente procurei no google primeiro e achei isso (mas não teria perguntado sem ler o manual). Eu meio que pensei que esse deveria ser o comportamento padrão, sem precisar de uma opção para ativá-lo.
JonnyJD
28
Infelizmente, isso não funciona em um Mac. Nem a opção curta nem a longa.
wedi
9
@wedi - O OSX tem espaço no usuário do BSD, então esse tipo de coisa acontece com frequência. Você pode contornar isso usando o homebrew para instalar os arquivos do GNU.
Edk750
7
Você quer dizer brew install findutils. findutilsnão fileutils.
Mitar
3
No macOS, não fornecer a flag resulta em não executar o comando de qualquer maneira, então acho que não é necessário.
9308 Trejkaz
15

Usuários de xargs não-GNU pode tirar proveito de -L <#lines>, -n <#args>, -i, e -I <string>:

ls /empty_dir/ | xargs -n10 chown root # chown executed every 10 args or fewer
ls /empty_dir/ | xargs -L10 chown root # chown executed every 10 lines or fewer
ls /empty_dir/ | xargs -i cp {} {}.bak # every {} is replaced with the args from one input line
ls /empty_dir/ | xargs -I ARG cp ARG ARG.bak # like -i, with a user-specified placeholder

Lembre-se de que o xargs divide a linha no espaço em branco, mas citações e escape estão disponíveis; RTFM para detalhes.

Além disso, como menciona Doron Behar, essa solução alternativa não é portátil, portanto, podem ser necessárias verificações:

$ uname -is
SunOS sun4v
$ xargs -n1 echo blah < /dev/null
$ uname -is
Linux x86_64
$ xargs --version | head -1
xargs (GNU findutils) 4.7.0-git
$ xargs -n1 echo blah < /dev/null
blah
arielCo
fonte
2
Não parece responder à pergunta?
Nicolas Raoul
2
@ Nicolas: é a maneira de impedir a execução se a sua versão do xargsnão suportar o --no-run-if-emptyswitch e tentar executar o argumento de comando sem a entrada STDIN (é o caso do Solaris 10). As versões em outros unixes podem simplesmente ignorar uma lista vazia (por exemplo, AIX).
ArielCo 21/07
4
Sim! No MAC OSX, echo '' | xargs -n1 echo blahnão faz nada; enquanto echo 'x' | xargs -n1 echo blahimprime "blá".
Carlosayam
2
Para o suporte ao GNU e ao BSD / OSX, acabo usando algo como:, ls /mydir/*.txt | xargs -n 1 -I {} chown root {}como esta resposta sugere.
Luís Bianchin
2
Note-se que echo '' | xargs -n1 echo blahimprime blahcom GNU xargs.
Doron Behar
11

man xargsdiz --no-run-if-empty.

thiton
fonte
1
Se você estiver no OSX, não o fará. edk750 explicou isso em seu comentário, se você está curioso por quê.
Jasonleonhard
9

Em termos de xargs, você pode usar -rcomo sugerido, no entanto, não é suportado pelo BSD xargs.

Portanto, como solução alternativa, você pode passar algum arquivo temporário extra, por exemplo:

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root $(mktemp)

ou redirecione seu stderr para null ( 2> /dev/null), por exemplo

find /mydir -type f -name "*.txt" -print0 | xargs -0 chown root 2> /dev/null || true

Outra abordagem melhor é iterar sobre os arquivos encontrados usando o whileloop:

find /mydir -type f -name "*.txt" -print0 | while IFS= read -r -d '' file; do
  chown -v root "$file"
done

Consulte também: Ignorar resultados vazios para xargs no Mac OS X


Observe também que seu método de alterar as permissões não é ótimo e é desencorajado. Definitivamente você não deve analisar a saída do lscomando (consulte: Por que você não deve analisar a saída de ls ). Especialmente quando você está executando seu comando pela raiz, porque seus arquivos podem conter caracteres especiais que podem ser interpretados pelo shell ou imaginar o arquivo com um caractere de espaço ao redor/ , os resultados podem ser terríveis.

Portanto, você deve alterar sua abordagem e usar o findcomando, por exemplo,

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
kenorb
fonte
1
Desculpe, não entendo como isso while IFS= read -r -d '' fileé válido. Você pode explicar?
Adrian
2

No OSX: Reimplementação Bash de como xargslidar com o -rargumento, insira isso, por exemplo, $HOME/bine adicione-o ao PATH:

#!/bin/bash
stdin=$(cat <&0)
if [[ $1 == "-r" ]] || [[ $1 == "--no-run-if-empty" ]]
then
    # shift the arguments to get rid of the "-r" that is not valid on OSX
    shift
    # wc -l return some whitespaces, let's get rid of them with tr
    linecount=$(echo $stdin | grep -v "^$" | wc -l | tr -d '[:space:]') 
    if [ "x$linecount" = "x0" ]
    then
      exit 0
    fi
fi

# grep returns an error code for no matching lines, so only activate error checks from here
set -e
set -o pipefail
echo $stdin | /usr/bin/xargs $@
rcomblen
fonte
2

Este é um comportamento do GNU xargs que pode ser suprimido usando -r, --no-run-if-empty.

A variante * BSD do xargs tem esse comportamento no padrão, portanto -r não é necessário. Desde o FreeBSD 7.1 (lançado em janeiro de 2009), um argumento -r é aceito (o que não faz nada) por razões de compatibilidade.

Pessoalmente, prefiro usar longopts em scripts, mas como o * BSD xargs não usa longopts, basta usar "-r" e o xargs agirá da mesma maneira nos sistemas * BSD e linux

O xargs no MacOS (atualmente o MacOS Mojave) infelizmente não suporta o argumento "-r".

Mirko Steiner
fonte