Wie ignoriere ich xargs-Befehle, wenn die stdin-Eingabe leer ist?

188

Betrachten Sie diesen Befehl:

ls /mydir/*.txt | xargs chown root

Es ist beabsichtigt, die Eigentümer aller Textdateien in mydirroot zu ändern

Das Problem ist, dass wenn xargs keine .txtDateien enthält, mydirxargs einen Fehler ausgibt, der besagt, dass kein Pfad angegeben ist. Dies ist ein harmloses Beispiel, da ein Fehler ausgegeben wird, aber in einigen Fällen, wie in dem Skript, das ich hier verwenden muss, wird angenommen, dass ein leerer Pfad das aktuelle Verzeichnis ist. Wenn ich diesen Befehl /home/tom/dann ausführe, wenn es kein Ergebnis für gibt ls /mydir/*.txtund alle Dateien unter /home/tom/ihren Besitzern in root geändert wurden.

Wie kann ich xargs ein leeres Ergebnis ignorieren lassen?

HyderA
quelle
6
Nebenbei: Niemals eine Ausgabe lsfür den programmatischen Gebrauch leiten. siehe mywiki.wooledge.org/ParsingLs
Charles Duffy
Mein Anwendungsfall ist git branch --merged | grep -v '^* ' | xargs git branch -d, dass auch bei leerer Eingabe fehlschlägt
belkka

Antworten:

305

Für GNU xargskönnen Sie die Option -roder verwenden --no-run-if-empty:

--no-run-if-empty
-r
Wenn die Standardeingabe keine Leerzeichen enthält, führen Sie den Befehl nicht aus. Normalerweise wird der Befehl einmal ausgeführt, auch wenn keine Eingabe erfolgt. Diese Option ist eine GNU-Erweiterung.

Sven Marnach
quelle
9
Ich habe ehrlich zuerst in Google gesucht und dies gefunden (hätte aber nicht gefragt, ohne das Handbuch gelesen zu haben). Ich dachte, dies wäre das Standardverhalten, da ich keinen Schalter benötige, um es zu aktivieren.
JonnyJD
28
Leider funktioniert dies auf einem Mac nicht. Weder die kurze noch die lange Option.
Mi
9
@wedi - OSX hat einen BSD-Benutzerbereich, daher wird so etwas häufig vorkommen. Sie können dies umgehen, indem Sie Homebrew verwenden, um die GNU-Fileutils zu installieren.
edk750
7
Du meinst brew install findutils. findutilsnicht fileutils.
Mitar
3
Unter macOS führt das Nicht-Bereitstellen des Flags dazu, dass der Befehl ohnehin nicht ausgeführt wird. Ich denke, er wird einfach nicht benötigt.
Trejkaz
15

Benutzer von nicht-GNU xargs nehmen können Vorteil -L <#lines>, -n <#args>, -i, und -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

Beachten Sie, dass xargs die Zeile in Leerzeichen aufteilt, aber Anführungszeichen und Escapezeichen verfügbar sind. RTFM für Details.

Wie Doron Behar erwähnt, ist diese Problemumgehung nicht portierbar, sodass möglicherweise Überprüfungen erforderlich sind:

$ 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
quelle
2
Scheint die Frage nicht zu beantworten?
Nicolas Raoul
2
@Nicolas: Auf diese Weise können Sie die Ausführung verhindern, wenn Ihre Version von xargsden --no-run-if-emptySwitch nicht unterstützt und versucht, das Befehlsargument ohne STDIN-Eingabe auszuführen (dies ist in Solaris 10 der Fall). Die Versionen in anderen Unixen ignorieren möglicherweise nur eine leere Liste (z. B. AIX).
ArielCo
4
Ja! Unter MAC OSX echo '' | xargs -n1 echo blahwird nichts ausgeführt. während echo 'x' | xargs -n1 echo blahdruckt "bla".
Carlosayam
2
Sowohl für die GNU- als auch für die BSD / OSX-Unterstützung verwende ich am Ende so etwas wie : ls /mydir/*.txt | xargs -n 1 -I {} chown root {}, wie diese Antwort nahelegt.
Luís Bianchin
2
Es ist zu beachten, dass mit GNUs echo '' | xargs -n1 echo blahgedruckt blahwird xargs.
Doron Behar
11

man xargssagt --no-run-if-empty.

Thiton
quelle
1
Wenn Sie unter OSX arbeiten, wird dies nicht der Fall sein. edk750 hat dies in ihrem Kommentar erklärt, wenn Sie neugierig sind, warum.
Jasonleonhard
9

In Bezug auf xargskönnen Sie -rwie vorgeschlagen verwenden, es wird jedoch von BSD nicht unterstützt xargs.

Um dieses Problem zu umgehen, können Sie eine zusätzliche temporäre Datei übergeben, zum Beispiel:

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

oder leiten Sie seinen stderr in null ( 2> /dev/null) um, z

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

Ein weiterer besserer Ansatz besteht darin, gefundene Dateien mit der whileSchleife zu durchlaufen :

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

Siehe auch: Ignorieren Sie leere Ergebnisse für xargs in Mac OS X.


Beachten Sie außerdem, dass Ihre Methode zum Ändern der Berechtigungen nicht besonders gut ist und nicht empfohlen wird. Auf jeden Fall sollten Sie nicht Parse - Ausgabe des lsBefehls (siehe: Warum sollte man nicht die Ausgabe von ls parsen ). Insbesondere wenn Sie Ihren Befehl über root ausführen, da Ihre Dateien Sonderzeichen enthalten können, die von der Shell interpretiert werden können, oder wenn Sie sich vorstellen, dass die Datei ein Leerzeichen enthält /, können die Ergebnisse schrecklich sein.

Daher sollten Sie Ihren Ansatz ändern und findstattdessen den Befehl verwenden, z

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
Kenorb
quelle
1
Entschuldigung, ich verstehe nicht, wie das while IFS= read -r -d '' filegültig ist. Können Sie erklären?
Adrian
2

Unter OSX: Bash-Neuimplementierung des xargsUmgangs mit dem -rArgument, setzen Sie das zB ein $HOME/binund fügen Sie es hinzu zu 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
quelle
2

Dies ist ein Verhalten von GNU-xargs, das mit -r, --no-run-if-empty unterdrückt werden kann.

Die * BSD-Variante von xargs hat dieses Verhalten standardmäßig, daher wird -r nicht benötigt. Seit FreeBSD 7.1 (veröffentlicht im Januar 2009) wird aus Kompatibilitätsgründen ein -r-Argument akzeptiert (was nichts tut).

Ich persönlich bevorzuge die Verwendung von Longopts in Skripten, aber da * BSD xargs keine Longopts verwendet, verwenden Sie einfach "-r" und xargs verhält sich auf * BSD und Linux-Systemen genauso

xargs unter MacOS (derzeit MacOS Mojave) unterstützt das Argument "-r" leider nicht.

Mirko Steiner
quelle