Как игнорировать команды xargs, если ввод stdin пуст?

191

Рассмотрим эту команду:

ls /mydir/*.txt | xargs chown root

Намерение состоит в том, чтобы изменить владельцев всех текстовых файлов в mydirroot

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

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

HyderA
источник
6
В сторону: Никогда не передавайте выходные данные lsдля программного использования; см. mywiki.wooledge.org/ParsingLs
Чарльз Даффи
Мой вариант использования git branch --merged | grep -v '^* ' | xargs git branch -d, который также не срабатывает при пустом вводе
belkka

Ответы:

307

Для 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
arielCo
источник
2
Кажется, не отвечает на вопрос?
Николас Рауль
2
@Nicolas: это способ предотвратить выполнение, если ваша версия xargsне поддерживает --no-run-if-emptyкоммутатор и пытается запустить аргумент команды без ввода STDIN (это имеет место в Solaris 10). Версии в других unixes могут просто игнорировать пустой список (например, AIX).
arielCo
4
Да! На MAC OSX echo '' | xargs -n1 echo blahничего не делает; тогда как echo 'x' | xargs -n1 echo blahпечатает "бла".
Карлосаям
2
Как для поддержки GNU, так и для BSD / OSX я в конечном итоге использовал что-то вроде:, ls /mydir/*.txt | xargs -n 1 -I {} chown root {}как предполагает этот ответ.
Луис Бьянчин
3
Следует отметить, что echo '' | xargs -n1 echo blahпечатает blahс GNU xargs.
Дорон Бехар
11

man xargsговорит --no-run-if-empty.

thiton
источник
1
Если вы используете OSX, это не так. edk750 объяснил это в своем комментарии, если вам интересно, почему.
Джейсон Леонард
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 ). Особенно, когда вы запускаете команду от имени пользователя root, поскольку ваши файлы могут состоять из специальных символов, которые могут интерпретироваться оболочкой или представлять файл с пробелом вокруг /, тогда результаты могут быть ужасными.

Поэтому вы должны изменить свой подход и использовать findвместо него команду, например

find /mydir -type f -name "*.txt" -execdir chown root {} ';'
kenorb
источник
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 $@
rcomblen
источник
2

Это поведение GNU xargs, которое может быть подавлено с помощью -r, --no-run-if-empty.

Вариант xargs * BSD имеет такое поведение по умолчанию, поэтому -r не требуется. Начиная с FreeBSD 7.1 (выпущенной в январе 2009 г.), аргумент -r принимается (ведьма ничего не делает) по соображениям совместимости.

Лично я предпочитаю использовать longopts в скриптах, но поскольку * BSD xargs не использует longopts, просто используйте «-r», и xargs будет действовать так же на * BSD и на системах Linux

К сожалению, xargs в MacOS (в настоящее время MacOS Mojave) не поддерживает аргумент "-r".

Мирко Штайнер
источник