Come ignorare i comandi xargs se l'input stdin è vuoto?


190

Considera questo comando:

ls /mydir/*.txt | xargs chown root

L'intenzione è di cambiare i proprietari di tutti i file di testo in mydirroot

Il problema è che se non ci sono .txtfile in mydirallora xargs genera un errore che dice che non è stato specificato alcun percorso. Questo è un esempio innocuo perché viene generato un errore, ma in alcuni casi, come nello script che devo usare qui, si presume che un percorso vuoto sia la directory corrente. Quindi se eseguo quel comando da /home/tom/allora se non ci sono risultati per ls /mydir/*.txte tutti i file sotto /home/tom/hanno i loro proprietari cambiati in root.

Quindi, come posso fare in modo che xargs ignori un risultato vuoto?


6
A parte: non convogliare mai l'uscita da lsper uso programmatico; vedi mywiki.wooledge.org/ParsingLs
Charles Duffy il

Il mio caso d'uso è git branch --merged | grep -v '^* ' | xargs git branch -d, che fallisce anche con input vuoto
belkka

Risposte:


306

Per GNU xargs, puoi usare l' opzione -ro --no-run-if-empty:

--no-run-if-empty
-r
Se l'input standard non contiene spazi vuoti, non eseguire il comando. Normalmente, il comando viene eseguito una volta anche se non è presente alcun input. Questa opzione è un'estensione GNU.


9
Ho cercato onestamente prima su Google e ho trovato questo (ma non avrei chiesto senza leggere il manuale). Ho pensato che questo dovrebbe essere il comportamento predefinito, senza bisogno di un interruttore per abilitarlo.
JonnyJD,

28
Purtroppo questo non funziona su un Mac. Né l'opzione corta né quella lunga.
wedi,

9
@wedi - OSX ha uno spazio utenti BSD, quindi questo genere di cose accadrà di frequente. Puoi aggirare il problema usando homebrew per installare i fileutils GNU.
edk750,

7
Intendi brew install findutils. findutilsno fileutils.
Mitar,

3
Su macOS, non fornire il flag comporta comunque la mancata esecuzione del comando, quindi immagino che non sia necessario.
Trejkaz,

15

Gli utenti di xargs non-GNU possono usufruire di -L <#lines>, -n <#args>, -ie -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

Tieni presente che xargs divide la linea negli spazi bianchi ma sono disponibili quotazioni e escape; RTFM per i dettagli.

Inoltre, come menziona Doron Behar, questa soluzione alternativa non è portatile, quindi potrebbero essere necessari controlli:

$ 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

2
Non sembra rispondere alla domanda?
Nicolas Raoul,

2
@Nicolas: è il modo per impedire l'esecuzione se la tua versione di xargsnon supporta lo --no-run-if-emptyswitch e tenta di eseguire l'argomento comando senza input STDIN (questo è il caso di Solaris 10). Le versioni in altri unix potrebbero semplicemente ignorare un elenco vuoto (ad es. AIX).
ariel:

4
Sì! Su MAC OSX, echo '' | xargs -n1 echo blahnon fa nulla; mentre echo 'x' | xargs -n1 echo blahstampa "blah".
carlosayam,

2
Sia per il supporto GNU che per BSD / OSX finisco per usare qualcosa del tipo:, come ls /mydir/*.txt | xargs -n 1 -I {} chown root {}suggerisce questa risposta.
Luís Bianchin,

3
Va notato che echo '' | xargs -n1 echo blahstampa blahcon GNU xargs.
Doron Behar,

11

man xargsdice --no-run-if-empty.


1
Se sei su OSX non lo farà. edk750 lo ha spiegato nel loro commento se sei curioso di sapere perché.
Jasonleonhard,

9

In termini di xargs, è possibile utilizzare -rcome suggerito, tuttavia non è supportato da BSD xargs.

Quindi, come soluzione alternativa, potresti passare qualche file temporaneo aggiuntivo, ad esempio:

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

o reindirizzare il suo stderr in null ( 2> /dev/null), ad es

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

Un altro approccio migliore è quello di scorrere i file trovati usando whileloop:

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

Vedi anche: Ignora risultati vuoti per xargs in Mac OS X.


Inoltre, tieni presente che il tuo metodo di modifica delle autorizzazioni non è eccezionale ed è scoraggiato. Sicuramente non dovresti analizzare l'output del lscomando (vedi: Perché non dovresti analizzare l'output di ls ). Soprattutto quando si esegue il comando da root, poiché i file possono contenere caratteri speciali che possono essere interpretati dalla shell o immaginare che il file abbia uno spazio attorno /, quindi i risultati possono essere terribili.

Pertanto, è necessario modificare l'approccio e utilizzare findinvece il comando, ad es

find /mydir -type f -name "*.txt" -execdir chown root {} ';'

1
Spiacenti, non capisco come while IFS= read -r -d '' filesia valido. Puoi spiegare?
Adrian,

2

Su OSX: reimplementazione di Bash nel xargstrattare l' -rargomento, inseriscilo ad esempio $HOME/bine aggiungilo a 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 $@

2

Questo è un comportamento degli xarg GNU che può essere soppresso usando -r, --no-run-if-empty.

La variante * BSD di xargs ha questo comportamento di default, quindi non è necessario -r. Da FreeBSD 7.1 (rilasciato a gennaio 2009) viene accettato un argomento -r (la strega non fa nulla) per motivi di compatibilità.

Personalmente preferisco usare longopts negli script, ma poiché xargs * BSD non usa longopts usa semplicemente "-r" e xargs agirà allo stesso modo su * BSD su sistemi linux

xargs su MacOS (attualmente MacOS Mojave) purtroppo non supporta l'argomento "-r".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.