¿Cómo ignorar los comandos xargs si la entrada estándar está vacía?

190

Considere este comando:

ls /mydir/*.txt | xargs chown root

La intención es cambiar los propietarios de todos los archivos de texto mydira raíz

El problema es que si no hay .txtarchivos en mydirxargs, aparece un error que indica que no hay una ruta especificada. Este es un ejemplo inofensivo porque se está generando un error, pero en algunos casos, como en el script que necesito usar aquí, se supone que una ruta en blanco es el directorio actual. Entonces, si ejecuto ese comando desde /home/tom/entonces, si no hay ningún resultado ls /mydir/*.txty todos los archivos bajo /home/tom/tienen sus propietarios cambiados a root.

Entonces, ¿cómo puedo hacer que xargs ignore un resultado vacío?

HyderA
fuente
66
Aparte: nunca canalice la salida de lspara uso programático; ver mywiki.wooledge.org/ParsingLs
Charles Duffy
Mi caso de uso es git branch --merged | grep -v '^* ' | xargs git branch -d, que también falla en la entrada vacía
belkka

Respuestas:

306

Para GNU xargs, puede usar la opción -ro --no-run-if-empty:

--no-run-if-empty
-r
Si la entrada estándar no contiene espacios en blanco, no ejecute el comando. Normalmente, el comando se ejecuta una vez, incluso si no hay entrada. Esta opción es una extensión GNU.

Sven Marnach
fuente
9
Honestamente busqué en Google primero y encontré esto (pero no habría preguntado sin leer el manual). Pensé que este debería ser el comportamiento predeterminado, que no necesita un interruptor para habilitarlo.
JonnyJD
28
Lamentablemente, esto no funciona en una Mac. Ni la opción corta ni la larga.
Mié
9
@wedi: OSX tiene espacio de usuario BSD, por lo que este tipo de cosas sucederán con frecuencia. Puede solucionarlo utilizando homebrew para instalar los archivos GNU.
edk750
77
¿Quiere decir brew install findutils. findutils, No fileutils.
Mitar
3
En macOS, no proporcionar el indicador resulta en no ejecutar el comando de todos modos, así que supongo que simplemente no es necesario.
Trejkaz
15

Los usuarios de GNU xargs no pueden hacer uso de -L <#lines>, -n <#args>, -i, y -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

Tenga en cuenta que xargs divide la línea en el espacio en blanco, pero hay comillas y escapes disponibles; RTFM para más detalles.

Además, como menciona Doron Behar, esta solución no es portátil, por lo que es posible que se necesiten controles:

$ 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
fuente
2
¿No parece responder la pregunta?
Nicolas Raoul
2
@Nicolas: es la forma de evitar la ejecución si su versión de xargsno admite el --no-run-if-emptyconmutador e intenta ejecutar el argumento del comando sin entrada STDIN (este es el caso en Solaris 10). Las versiones en otros unixes pueden ignorar una lista vacía (por ejemplo, AIX).
arielCo
44
¡Si! En MAC OSX, echo '' | xargs -n1 echo blahno hace nada; mientras que echo 'x' | xargs -n1 echo blahimprime "bla".
carlosayam
2
Para el soporte de GNU y BSD / OSX termino usando algo como:, ls /mydir/*.txt | xargs -n 1 -I {} chown root {}como sugiere esta respuesta.
Luís Bianchin
3
Cabe señalar que echo '' | xargs -n1 echo blahimprime blahcon GNU's xargs.
Doron Behar
11

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

thiton
fuente
1
Si está en OSX, no lo hará. edk750 ha explicado esto en su comentario si tienes curiosidad por qué.
jasonleonhard
9

En términos de xargs, puede usar -rcomo se sugiere, sin embargo, BSD no lo admite xargs.

Entonces, como solución alternativa, puede pasar algún archivo temporal adicional, por ejemplo:

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

o redirigir su stderr a nulo ( 2> /dev/null), por ejemplo

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

Otro mejor enfoque es iterar sobre los archivos encontrados usando el whilebucle:

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

Consulte también: Ignorar resultados vacíos para xargs en Mac OS X


También tenga en cuenta que su método para cambiar los permisos no es excelente y se desaconseja. Definitivamente no debe analizar la salida del lscomando (consulte: Por qué no debe analizar la salida de ls ). Especialmente cuando está ejecutando su comando por root, porque sus archivos pueden consistir en caracteres especiales que pueden ser interpretados por el shell o imaginar que el archivo tiene un carácter de espacio alrededor /, entonces los resultados pueden ser terribles.

Por lo tanto, debe cambiar su enfoque y usar el findcomando en su lugar, por ejemplo

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
kenorb
fuente
1
Lo siento, no entiendo cómo while IFS= read -r -d '' filees válido. ¿Puedes explicar?
Adrian
2

En OSX: reimplementación de Bash para xargstratar el -rargumento, póngalo, por ejemplo, $HOME/biny agréguelo 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 $@
rcomblen
fuente
2

Este es un comportamiento de GNU xargs que puede suprimirse mediante el uso de -r, --no-run-if-empty.

La variante * BSD de xargs tiene este comportamiento predeterminado, por lo que no se necesita -r. Desde FreeBSD 7.1 (lanzado en enero de 2009) se acepta un argumento -r (que no hace nada) por razones de compatibilidad.

Personalmente prefiero usar longopts en scripts, pero dado que * BSD xargs no usa longopts, solo use "-r" y xargs actuará de la misma manera en * BSD y en sistemas Linux

xargs en MacOS (actualmente MacOS Mojave) lamentablemente no admite el argumento "-r".

Mirko Steiner
fuente