Comment ignorer les commandes xargs si l'entrée stdin est vide?

191

Considérez cette commande:

ls /mydir/*.txt | xargs chown root

L'intention est de changer les propriétaires de tous les fichiers texte en mydirroot

Le problème est que s'il n'y a pas de .txtfichiers, mydirxargs génère une erreur indiquant qu'aucun chemin n'est spécifié. Ceci est un exemple inoffensif car une erreur est générée, mais dans certains cas, comme dans le script que je dois utiliser ici, un chemin vide est supposé être le répertoire actuel. Donc, si j'exécute cette commande à partir de /home/tom/là, s'il n'y a pas de résultat pour ls /mydir/*.txtet que tous les fichiers sous /home/tom/ont leurs propriétaires changés en root.

Alors, comment puis-je faire ignorer un résultat vide par xargs?

HyderA
la source
6
À part: ne jamais diriger la sortie de lspour une utilisation programmatique; voir mywiki.wooledge.org/ParsingLs
Charles Duffy
Mon cas d'utilisation est git branch --merged | grep -v '^* ' | xargs git branch -d, qui échoue également sur une entrée vide
belkka

Réponses:

307

Pour GNU xargs, vous pouvez utiliser l' option -rou --no-run-if-empty:

--no-run-if-empty
-r
Si l'entrée standard ne contient aucun élément non vide, n'exécutez pas la commande. Normalement, la commande est exécutée une fois même s'il n'y a pas d'entrée. Cette option est une extension GNU.

Sven Marnach
la source
9
Honnêtement, j'ai d'abord cherché dans Google et j'ai trouvé ceci (mais je n'aurais pas demandé sans lire le manuel). J'ai en quelque sorte pensé que ce devrait être le comportement par défaut, sans avoir besoin d'un commutateur pour l'activer.
JonnyJD
28
Malheureusement, cela ne fonctionne pas sur un Mac. Ni l'option courte ni l'option longue.
wedi
9
@wedi - OSX a un espace utilisateur BSD, donc ce genre de chose se produira fréquemment. Vous pouvez contourner ce problème en utilisant homebrew pour installer les fileutils GNU.
edk750
7
Tu veux dire brew install findutils. findutilsnon fileutils.
Mitar
3
Sur macOS, ne pas fournir l'indicateur entraîne de toute façon de ne pas exécuter la commande, donc je suppose que ce n'est tout simplement pas nécessaire.
Trejkaz
15

Les utilisateurs de GNU xargs non peuvent tirer profit de -L <#lines>, -n <#args>, -iet -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

Gardez à l'esprit que xargs divise la ligne en espace mais que les guillemets et les échappements sont disponibles; RTFM pour plus de détails.

De plus, comme le mentionne Doron Behar, cette solution de contournement n'est pas portable, des vérifications peuvent donc être nécessaires:

$ 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
la source
2
Ne semble pas répondre à la question?
Nicolas Raoul
2
@Nicolas: c'est le moyen d'empêcher l'exécution si votre version de xargsne prend pas en charge le --no-run-if-emptycommutateur et tente d'exécuter l'argument de commande sans entrée STDIN (c'est le cas dans Solaris 10). Les versions dans d'autres Unix peuvent simplement ignorer une liste vide (par exemple AIX).
arielCo
4
Oui! Sur MAC OSX, echo '' | xargs -n1 echo blahne fait rien; alors echo 'x' | xargs -n1 echo blahqu'imprime "bla".
carlosayam
2
Pour le support GNU et BSD / OSX, je finis par utiliser quelque chose comme:, comme le ls /mydir/*.txt | xargs -n 1 -I {} chown root {}suggère cette réponse.
Luís Bianchin
3
Il convient de noter que les echo '' | xargs -n1 echo blahimpressions blahavec GNU xargs.
Doron Behar
11

man xargsdit --no-run-if-empty.

thiton
la source
1
Si vous êtes sur OSX, ce ne sera pas le cas. edk750 a expliqué cela dans son commentaire si vous êtes curieux de savoir pourquoi.
jasonleonhard
9

En termes de xargs, vous pouvez utiliser -rcomme suggéré, mais il n'est pas pris en charge par BSD xargs.

Ainsi, pour contourner le problème, vous pouvez transmettre un fichier temporaire supplémentaire, par exemple:

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

ou rediriger son stderr vers null ( 2> /dev/null), par exemple

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

Une autre meilleure approche consiste à parcourir les fichiers trouvés en utilisant la whileboucle:

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

Voir aussi: Ignorer les résultats vides pour xargs sous Mac OS X


Veuillez également noter que votre méthode de modification des autorisations n'est pas excellente et qu'elle est déconseillée. Vous ne devriez certainement pas analyser la sortie de la lscommande (voir: Pourquoi vous ne devriez pas analyser la sortie de ls ). Surtout lorsque vous exécutez votre commande par root, parce que vos fichiers peuvent être constitués de caractères spéciaux qui peuvent être interprétés par le shell ou imaginer que le fichier a un caractère d'espace autour /, alors les résultats peuvent être terribles.

Par conséquent, vous devriez changer votre approche et utiliser la findcommande à la place, par exemple

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
Kenorb
la source
1
Désolé, je ne comprends pas en quoi le while IFS= read -r -d '' fileest valide. Peux-tu expliquer?
Adrian
2

Sous OSX: réimplémentation Bash de la xargsgestion de l' -rargument, mettez-le par exemple $HOME/binet ajoutez-le au 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
la source
2

C'est un comportement de GNU xargs qui peut être supprimé en utilisant -r, --no-run-if-empty.

La variante * BSD de xargs a ce comportement par défaut, donc -r n'est pas nécessaire. Depuis FreeBSD 7.1 (sorti en janvier 2009), un argument -r est accepté (qui ne fait rien) pour des raisons de compatibilité.

Personnellement, je préfère utiliser longopts dans les scripts mais comme le * BSD xargs n'utilise pas longopts, utilisez simplement "-r" et xargs agira de la même manière sur * BSD et sur les systèmes Linux

xargs sur MacOS (actuellement MacOS Mojave) ne supporte malheureusement pas l'argument "-r".

Mirko Steiner
la source