Jak zignorować polecenia xargs, jeśli wejście stdin jest puste?

189

Rozważ to polecenie:

ls /mydir/*.txt | xargs chown root

Intencją jest zmiana właścicieli wszystkich plików tekstowych mydirna root

Problem polega na tym, że jeśli nie ma żadnych .txtplików, mydirxargs wyświetla błąd informujący, że nie określono ścieżki. Jest to nieszkodliwy przykład, ponieważ generowany jest błąd, ale w niektórych przypadkach, na przykład w skrypcie, którego muszę tutaj użyć, przyjmuje się, że bieżącym katalogiem jest pusta ścieżka. Więc jeśli uruchomię to polecenie /home/tom/odtąd, jeśli nie będzie wyników dla, ls /mydir/*.txta wszystkie pliki w katalogu /home/tom/mają swoich właścicieli zmienionych na root.

Więc jak mogę xargs zignorować pusty wynik?

HyderA
źródło
6
Poza tym: nigdy nie przesyłaj danych wyjściowych lsdo celów programowych; patrz mywiki.wooledge.org/ParsingLs
Charles Duffy
Mój przypadek użycia jest taki git branch --merged | grep -v '^* ' | xargs git branch -d, który również nie działa przy pustych danych wejściowych
belkka

Odpowiedzi:

305

W przypadku GNU xargsmożesz użyć opcji -rlub --no-run-if-empty:

--no-run-if-empty
-r
Jeśli standardowe wejście nie zawiera żadnych niepustych danych, nie uruchamiaj polecenia. Zwykle polecenie jest uruchamiane raz, nawet jeśli nie ma danych wejściowych. Ta opcja jest rozszerzeniem GNU.

Sven Marnach
źródło
9
Najpierw szczerze szukałem w Google i znalazłem to (ale nie zapytałbym bez przeczytania instrukcji). Pomyślałem, że to powinno być zachowanie domyślne, nie wymagający przełącznika, aby to umożliwić.
JonnyJD
28
Niestety nie działa to na komputerze Mac. Ani krótka, ani długa opcja.
śr.
9
@wedi - OSX ma przestrzeń użytkownika BSD, więc takie rzeczy zdarzają się często. Możesz obejść ten problem, używając homebrew do instalacji plików GNU.
edk750,
7
Masz na myśli brew install findutils. findutilsNie fileutils.
Mitar
3
W systemie macOS niepodanie flagi powoduje, że polecenie i tak nie jest uruchomione, więc myślę, że po prostu nie jest potrzebne.
Trejkaz,
15

Użytkownicy xargs non-gnu mogą skorzystać z -L <#lines>, -n <#args>, -ii -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

Należy pamiętać, że xargs dzieli linię w spacji, ale dostępne są cytowania i zmiany znaczenia; RTFM po szczegóły.

Ponadto, jak wspomina Doron Behar, to obejście nie jest przenośne, więc mogą być potrzebne kontrole:

$ 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
źródło
2
Nie wydaje się odpowiadać na pytanie?
Nicolas Raoul,
2
@Nicolas: jest to sposób na zapobieganie wykonywaniu, jeśli twoja wersja xargsnie obsługuje --no-run-if-emptyprzełącznika i próbuje uruchomić argument polecenia bez danych wejściowych STDIN (tak jest w Solarisie 10). Wersje innych unixów mogą po prostu zignorować pustą listę (np. AIX).
arielCo
4
Tak! W systemie MAC OSX echo '' | xargs -n1 echo blahnic nie robi; podczas gdy echo 'x' | xargs -n1 echo blahdrukuje „bla”.
carlosayam
2
Zarówno w przypadku obsługi GNU, jak i BSD / OSX używam czegoś takiego jak: ls /mydir/*.txt | xargs -n 1 -I {} chown root {}jak sugeruje ta odpowiedź.
Luís Bianchin
3
Należy zauważyć, że echo '' | xargs -n1 echo blahdrukuje blahz GNU xargs.
Doron Behar
11

man xargsmówi --no-run-if-empty.

Thiton
źródło
1
Jeśli korzystasz z OSX, nie będzie. edk750 wyjaśnił to w swoim komentarzu, jeśli jesteś ciekawy, dlaczego.
jasonleonhard
9

Jeśli chodzi o xargs, możesz używać -rzgodnie z sugestią, jednak nie jest to obsługiwane przez BSD xargs.

Aby obejść ten problem, możesz przekazać dodatkowy plik tymczasowy, na przykład:

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

lub przekieruj jego stderr na null ( 2> /dev/null), np

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

Innym lepszym podejściem jest iteracja znalezionych plików za pomocą whilepętli:

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

Zobacz także: Zignoruj ​​puste wyniki dla xargs w Mac OS X.


Pamiętaj również, że twoja metoda zmiany uprawnień nie jest świetna i jest odradzana. Zdecydowanie nie powinieneś analizować danych wyjściowych lspolecenia (zobacz: Dlaczego nie powinieneś analizować danych wyjściowych polecenia ls ). Zwłaszcza gdy uruchamiasz polecenie przez root, ponieważ twoje pliki mogą składać się ze znaków specjalnych, które mogą być interpretowane przez powłokę lub wyobrażać sobie plik zawierający znak spacji/ , wtedy wyniki mogą być straszne.

Dlatego powinieneś zmienić swoje podejście i findzamiast tego użyć polecenia, np

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
kenorb
źródło
1
Przepraszam, nie rozumiem, jak while IFS= read -r -d '' fileważne jest. Możesz wytłumaczyć?
Adrian
2

W OSX: Bash reimplementation xargsradzenia sobie z -rargumentem, wstaw to np. Do $HOME/bini dodaj go do 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
źródło
2

Jest to zachowanie GNU xargs, które można stłumić za pomocą opcji -r, --no-run-if-empty.

Wariant * BSD xargs ma takie zachowanie domyślnie, więc -r nie jest potrzebne. Od wersji FreeBSD 7.1 (wydanej w styczniu 2009) argument -r jest akceptowany (czarownica nic nie robi) z powodów kompatybilności.

Osobiście wolę używać skryptów longopts, ale ponieważ xargs * BSD nie używa longopsów, użyj po prostu „-r”, a xargs będzie działał tak samo na * BSD i systemach Linux.

xargs na MacOS (obecnie MacOS Mojave) niestety nie obsługuje argumentu „-r”.

Mirko Steiner
źródło