Проверьте, выводит ли команда пустую строку

237

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

barp
источник
7
Команда не возвращает строку (она возвращает небольшой целочисленный код завершения, обычно 0 при успехе и 1 или 2 при ошибке), но она может вывести некоторые данные для помещения в строку.
Василий Старынкевич,
@BasileStarynkevitch - это то, что мне нужно. Я знаю, что команда возвращает код выхода. Но код выхода моей команды всегда равен 0., поэтому мне нужно контролировать вывод
barp
1
Вы должны прочитать некоторые Bash скрипты учебник tldp.org/LDP/abs/html
Basile Starynkevitch
У Джои Хесса есть утилита ifneдля этого. joeyh.name/code/moreutils
tripleee

Ответы:

309

Ранее задавался вопрос, как проверить, есть ли файлы в каталоге. Следующий код достигает этого, но см . Ответ rsp для лучшего решения.


Пустой вывод

Команды не возвращают значения - они выводят их. Вы можете захватить этот вывод, используя подстановку команд ; например $(ls -A). Вы можете проверить непустую строку в Bash следующим образом:

if [[ $(ls -A) ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Обратите внимание, что я использовал -Aвместо -a, так как он пропускает символические записи каталога current ( .) и parent ( ..).

Примечание. Как указано в комментариях, подстановка команд не захватывает завершающие символы новой строки . Следовательно, если команда выводит только новые строки, подстановка ничего не захватывает, а тест возвращает false. Хотя это очень маловероятно, это возможно в приведенном выше примере, поскольку одна новая строка является допустимым именем файла! Больше информации в этом ответе .


Код выхода

Если вы хотите проверить, успешно ли выполнена команда, вы можете проверить $?, в которой содержится код завершения последней команды (ноль для успеха, ненулевое значение для сбоя). Например:

files=$(ls -A)
if [[ $? != 0 ]]; then
    echo "Command failed."
elif [[ $files ]]; then
    echo "Files found."
else
    echo "No files found."
fi

Больше информации здесь .

Уилл Вусден
источник
4
Вы можете опустить -n , так что это будет простоif [[ $(ls -A) ]]; then
пользователь
6
Этот метод выдает false для команд, которые выводят только символы новой строки.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
89

TL; DR

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then ...; fi

Спасибо netj за предложение улучшить мой оригинал:
if [[ $(ls -A | wc -c) -ne 0 ]]; then ...; fi


Это старый вопрос, но я вижу, по крайней мере, две вещи, которые нуждаются в некотором улучшении или, по крайней мере, в некотором уточнении.

Первая проблема

Первая проблема, которую я вижу, состоит в том, что большинство приведенных здесь примеров просто не работают . Они используют ls -alи ls -Alкоманды - оба из которых выход не пустые строки в пустых каталогов. Эти примеры всегда сообщают, что есть файлы, даже если их нет .

По этой причине вы должны использовать просто ls -A- Почему кто-то захочет использовать -lпереключатель, который означает «использовать формат длинного списка», когда все, что вам нужно, это проверить, есть ли какие-либо выходные данные или нет?

Поэтому большинство ответов здесь просто неверны.

Вторая проблема

Вторая проблема заключается в том , что в то время как некоторые ответы работают нормально (те , которые не используют ls -alили , ls -Alно ls -Aвместо этого) все они делают что - то вроде этого:

  1. запустить команду
  2. буферизировать весь вывод в ОЗУ
  3. преобразовать вывод в огромную однострочную строку
  4. сравнить эту строку с пустой строкой

Вместо этого я бы предложил сделать следующее:

  1. запустить команду
  2. считать символы в своем выводе, не сохраняя их
    • или даже лучше - посчитайте количество максимально 1 символом using head -c1
      (спасибо netj за публикацию этой идеи в комментариях ниже)
  3. сравнить это число с нулем

Так, например, вместо:

if [[ $(ls -A) ]]

Я хотел бы использовать:

if [[ $(ls -A | wc -c) -ne 0 ]]
# or:
if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]

Вместо того:

if [ -z "$(ls -lA)" ]

Я хотел бы использовать:

if [ $(ls -lA | wc -c) -eq 0 ]
# or:
if [ $(ls -lA | head -c1 | wc -c) -eq 0 ]

и так далее.

Для небольших выходов это может не быть проблемой, но для больших выходов разница может быть значительной:

$ time [ -z "$(seq 1 10000000)" ]

real    0m2.703s
user    0m2.485s
sys 0m0.347s

Сравните это с:

$ time [ $(seq 1 10000000 | wc -c) -eq 0 ]

real    0m0.128s
user    0m0.081s
sys 0m0.105s

И даже лучше:

$ time [ $(seq 1 10000000 | head -c1 | wc -c) -eq 0 ]

real    0m0.004s
user    0m0.000s
sys 0m0.007s

Полный пример

Обновленный пример из ответа Уилла Воудена:

if [[ $(ls -A | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Обновляется снова после предложений от netj :

if [[ $(ls -A | head -c1 | wc -c) -ne 0 ]]; then
    echo "there are files"
else
    echo "no files found"
fi

Дополнительное обновление от jakeonfire :

grepвыйдет с ошибкой, если совпадений нет. Мы можем воспользоваться этим, чтобы немного упростить синтаксис:

if ls -A | head -c1 | grep -E '.'; then
    echo "there are files"
fi

if ! ls -A | head -c1 | grep -E '.'; then
    echo "no files found"
fi

Отбрасывание пробелов

Если команда, которую вы тестируете, может вывести некоторые пробелы, которые вы хотите рассматривать как пустую строку, то вместо:

| wc -c

Вы можете использовать:

| tr -d ' \n\r\t ' | wc -c

или с head -c1:

| tr -d ' \n\r\t ' | head -c1 | wc -c

или что-то вроде того.

Резюме

  1. Сначала используйте команду, которая работает .

  2. Во-вторых, избегайте ненужного хранения в оперативной памяти и обработки потенциально огромных данных.

В ответе не указывалось, что выходные данные всегда малы, поэтому необходимо учитывать возможность больших выходных данных.

RSP
источник
3
[[ $(... | head -c | wc -c) -gt 0 ]]или [[ -n $(... | head -c1) ]]лучше, потому wc -cчто придется использовать весь вывод команды.
нет
@netj Это очень хорошая идея. Смотрите мой обновленный ответ - я добавил ваше предложение. Большое спасибо!
RSP
Любые идеи о том, как получить выход?
fahrradflucht
Я думаю, что оригинал (без головы) лучше в общем случае. Не всегда хорошая идея убить команду, как только она начнет писать вывод!
СТК
@stk Большинство программ благополучно завершат работу после получения сигнала уничтожения.
cambunctious
40
if [ -z "$(ls -lA)" ]; then
  echo "no files found"
else
  echo "There are files"
fi

Это запустит команду и проверит, имеет ли возвращаемый вывод (строка) нулевую длину. Возможно, вы захотите проверить справочные страницы «test» на наличие других флагов.

Используйте «» вокруг проверяемого аргумента, иначе пустые результаты приведут к синтаксической ошибке, поскольку второй аргумент (для проверки) не задан!

Обратите внимание, что ls -la всегда возвращается .и ..поэтому использование этого не будет работать, см. Справочные страницы . Кроме того, хотя это может показаться удобным и легким, я полагаю, что оно легко сломается. Написание небольшого скрипта / приложения, которое возвращает 0 или 1 в зависимости от результата, гораздо надежнее!

Veger
источник
Вы можете предпочесть использовать $(вместо обратной цитаты, потому что $(гнездо лучше
Василий Старынкевич
Вы правы, лучше практиковать их всегда, хотя нам здесь не нужно.
Вегер
23

Для тех, кто хочет элегантное решение, не зависящее от версии bash (на самом деле должно работать в других современных оболочках), и тех, кто любит использовать однострочники для быстрых задач. Вот так!

ls | grep . && echo 'files found' || echo 'files not found'

(обратите внимание, как один из упомянутых комментариев, ls -alи на самом деле, просто -lи -aвсе что-то вернут, поэтому в своем ответе я использую простойls

Alex
источник
5
Если вы используете grep -q ^вместо этого, символы новой строки также будут сопоставлены, и grep не будет ничего выводить на стандартный вывод. Кроме того, он выйдет, как только получит какие-либо входные данные, вместо того, чтобы ждать окончания входного потока.
mortehu
Должно начинаться с того, ls -Aесли вы хотите включить точечные файлы (или каталоги)
wrlee
13

Справочное руководство Bash

6.4 Условные выражения Bash

-z string
     True if the length of string is zero.

-n string
string
     True if the length of string is non-zero.

Вы можете использовать сокращенную версию:

if [[ $(ls -A) ]]; then
  echo "there are files"
else
  echo "no files found"
fi
пользователь
источник
1
В скобках отсутствует -z или -n [[...]].
71GA
4
@ 71GA: Возможно, это легко упустить из виду, но если вы посмотрите внимательно, вы увидите, что это просто stringсиноним -n string.
пользователь
10

Как прокомментировал Джон Лин, ls -alвсегда будет выводить (для .и ..). Вы хотите ls -Alизбежать этих двух каталогов.

Например, вы можете поместить вывод команды в переменную оболочки:

v=$(ls -Al)

Старая, нестабильная запись

v=`ls -Al`

но я предпочитаю вложенные обозначения $(...)

Вы можете проверить, если эта переменная не пуста

if [ -n "$v" ]; then
    echo there are files
else
    echo no files
fi

И вы могли бы объединить как if [ -n "$(ls -Al)" ]; then

Василий Старынкевич
источник
5

Я предполагаю, что вы хотите вывод ls -alкоманды, поэтому в bash вы получите что-то вроде:

LS=`ls -la`

if [ -n "$LS" ]; then
  echo "there are files"
else
  echo "no files found"
fi
Джон Лин
источник
Это не работает: команда lsникогда не выполняется. Вы только проверяете, является ли раскрытие переменной LS непустым.
gniourf_gniourf
1
@gniourf_gniourf - что заставляет вас так думать? Заклинание обратного удара, по общему признанию, не так красиво и гибко, как $ (), но оно работает ...
tink
-aВыключатель никогда не будет возвращать никакого содержания (то есть, он будет возвращать содержимое , есть ли файлы или нет) , так как всегда есть неявная .и ..каталоги присутствуют.
wrlee
3

Вот решение для более экстремальных случаев:

if [ `command | head -c1 | wc -c` -gt 0 ]; then ...; fi

Это будет работать

  • для всех раковин Борна;
  • если команда выводит все нули;
  • эффективно независимо от выходного размера;

тем не мение,

  • команда или ее подпроцессы будут уничтожены после вывода чего-либо.
односложный
источник
1

Все приведенные ответы касаются команд, которые завершают и выводят непустую строку.

Большинство из них сломаны в следующих смыслах:

  • Они не работают должным образом с командами, выводящими только новые строки;
  • начиная с Bash≥4.4, большинство из них будет содержать стандартную ошибку, если команда выводит нулевые байты (поскольку они используют подстановку команд);
  • большинство будет отбрасывать полный поток вывода, поэтому дождется завершения команды до ответа. Некоторые команды никогда не завершаются (попробуйте, например, yes).

Таким образом, чтобы исправить все эти проблемы и эффективно ответить на следующий вопрос,

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

ты можешь использовать:

if read -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Вы можете также добавить некоторые тайм - аут для того, чтобы тест выдает ли команда не пустую строку в данный момент времени, используя read«s -tварианта. Например, в течение 2,5 секунд:

if read -t2.5 -n1 -d '' < <(command_here); then
    echo "Command outputs something"
else
    echo "Command doesn't output anything"
fi

Замечание. Если вы считаете, что вам нужно определить, выводит ли команда непустую строку, у вас, скорее всего, проблема XY.

gniourf_gniourf
источник
Я думаю, что этот ответ недооценивается. Я производительность тестирования некоторые из решений здесь , используя 100 итераций для каждого из 3 -х входных сценариев ( seq 1 10000000, seq 1 20и printf""). Единственный матч, который этот подход чтения не выиграл, был с -z "$(command)"подходом для пустого ввода. Даже тогда он проигрывает только на 10-15%. Кажется, они безубыточны seq 1 10. Несмотря на это, -z "$(command)"подход становится настолько медленным с ростом размера ввода, что я бы избегал использовать его для любого ввода переменного размера.
Abathur
0

Вот альтернативный подход, который записывает std-out и std-err некоторой команды во временный файл, а затем проверяет, является ли этот файл пустым. Преимущество этого подхода состоит в том, что он захватывает оба выхода и не использует подоболочки или каналы. Эти последние аспекты важны, потому что они могут помешать перехвату обработки выхода bash (например, здесь )

tmpfile=$(mktemp)
some-command  &> "$tmpfile"
if [[ $? != 0 ]]; then
    echo "Command failed"
elif [[ -s "$tmpfile" ]]; then
    echo "Command generated output"
else
    echo "Command has no output"
fi
rm -f "$tmpfile"
Даррен Смит
источник
0

Иногда вы хотите сохранить вывод, если он не пустой, чтобы передать его другой команде. Если это так, вы можете использовать что-то вроде

list=`grep -l "MY_DESIRED_STRING" *.log `
if [ $? -eq 0 ]
then
    /bin/rm $list
fi

Таким образом, rmкоманда не будет зависать, если список пуст.

Скотт К Уилсон
источник