Як ігнорувати команди xargs, якщо вхід stdin порожній?


189

Розглянемо цю команду:

ls /mydir/*.txt | xargs chown root

Наміром є заміна власників усіх текстових файлів у mydirroot

Проблема полягає в тому, що якщо у .txtфайлах немає файлів mydirxargs, помилка говорить про те, що шлях не вказаний. Це нешкідливий приклад, тому що помилка кидається, але в деяких випадках, як у сценарії, який мені потрібно тут використовувати, пустий шлях вважається поточним каталогом. Тож якщо я запускаю цю команду з /home/tom/тих пір, якщо немає результату для, ls /mydir/*.txtа всі файли під /home/tom/ними змінили власників на root.

Тож як я можу з xargs ігнорувати порожній результат?


6
Убік: ніколи не виводити трубку lsдля програмного використання; дивіться mywiki.wooledge.org/ParsingLs
Чарльз Даффі

Мій випадок використання git branch --merged | grep -v '^* ' | xargs git branch -d, який також не вдається при порожньому введенні
belkka

Відповіді:


305

Для GNU xargsможна скористатись -rабо --no-run-if-emptyопцією:

--no-run-if-empty
-r
Якщо стандартний вхід не містить жодних пробілів, не виконуйте команду. Зазвичай команда виконується один раз, навіть якщо немає вводу. Цей параметр є розширенням GNU.


9
Я чесно шукав спочатку в Google і знайшов це (але не запитав би, не прочитавши посібник). Я начебто думав, що це може бути поведінка за замовчуванням, не потрібен перемикач, щоб увімкнути це.
JonnyJD

28
На жаль, це не працює на Mac. Ні короткий, ні довгий варіант.
весілля

9
@wedi - OSX має користувацький простір BSD, тому подібні речі трапляються часто. Ви можете обійти його, використовуючи homebrew для встановлення файлових файлів GNU.
edk750

7
Ви маєте на увазі brew install findutils. findutils, ні fileutils.
Мітар

3
У macOS ненадання прапора призводить до того, що команда все одно не виконує команду, тому я думаю, що це просто не потрібно.
Трежказ

15

Його користувачі не-GNU xargs може скористатися -L <#lines>, -n <#args>, -iі -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

Майте на увазі, що xargs розбиває лінію на пробіл, але котирування та втеча доступні; RTFM для деталей.

Крім того, як згадує Дорон Бехар, це рішення не є портативним, тому можуть знадобитися перевірки:

$ 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
Здається, не відповідає на питання?
Ніколя Рауль

2
@Nicolas: це спосіб запобігти виконанню, якщо ваша версія xargsне підтримує --no-run-if-emptyкомутатор і намагається запустити аргумент команди без введення STDIN (це так у Solaris 10). Версії в інших Unix можуть просто ігнорувати порожній список (наприклад, AIX).
arielCo

4
Так! На MAC OSX echo '' | xargs -n1 echo blahнічого не робить; тоді як echo 'x' | xargs -n1 echo blahвідбитки "бла".
carlosayam

2
І для підтримки GNU, і для BSD / OSX я в кінцевому підсумку використовую щось на кшталт:, ls /mydir/*.txt | xargs -n 1 -I {} chown root {}як це підказує ця відповідь.
Luís Bianchin

3
Слід зазначити, що echo '' | xargs -n1 echo blahпринти blahз GNU xargs.
Дорон Бехар

11

man xargsкаже --no-run-if-empty.


1
Якщо ви перебуваєте на OSX, це не буде. edk750 пояснив це у своєму коментарі, якщо вам цікаво чому.
Jasonleonhard

9

З точки зору xargs, ви можете використовувати, -rяк пропонується, однак це не підтримується BSD xargs.

Отже, як вирішення проблеми, ви можете передати додатковий тимчасовий файл, наприклад:

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

або перенаправити його stderr в null ( 2> /dev/null), наприклад

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

Ще один кращий підхід полягає в перегляді знайдених файлів за допомогою whileциклу:

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

Дивіться також: Ігноруйте порожні результати для xargs в Mac OS X


Також зверніть увагу, що ваш метод зміни дозволів не є великим, і це не рекомендується. Однозначно, ви не повинні розбирати вихід lsкоманди (див. Чому ви не повинні розбирати висновок ls ). Особливо, коли ви виконуєте свою команду під корінням, тому що ваші файли можуть складатися із спеціальних символів, які можуть бути інтерпретовані оболонкою або уявити, що файл має пробільний символ /, тоді результати можуть бути жахливими.

Тому слід змінити свій підхід і використовувати findзамість цього команду, наприклад

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

1
Вибачте, я не розумію, наскільки while IFS= read -r -d '' fileдіє. Ти можеш пояснити?
Адріан

2

На OSX: Bash повторне виконання xargsсправи з -rаргументом, введіть це, наприклад, у $HOME/binта додайте його до 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

Це поведінка xargs GNU, яку можна придушити, використовуючи -r, --no-run-if-empty.

У варіанті * BSD xargs за замовчуванням є така поведінка, тому -r не потрібен. Оскільки FreeBSD 7.1 (вийшов у січні 2009 року), аргумент -r прийнято (відьма нічого не робить) з міркувань сумісності.

Я особисто вважаю за краще використовувати longopts у скриптах, але оскільки * BSD xargs не використовує longopts, просто використовуйте "-r", і xargs буде діяти однаково на * BSD a в системах Linux

xargs на MacOS (зараз MacOS Mojave), на жаль, не підтримує аргумент "-r".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.