Баш, если не несколько условий без подоболочки?

23

Я хочу объединить несколько условий в операторе if if и отменить комбинацию. У меня есть следующий рабочий код для простой комбинации условий:

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ; then
  # do stuff with the files
fi

Это отлично работает. Если я хочу отрицать это, я могу использовать следующий рабочий код:

if ! ( [ -f file1 ] && [ -f file2 ] && [ -f file3 ] ) ; then
  echo "Error: You done goofed."
  exit 1
fi
# do stuff with the files

Это также работает как ожидалось. Однако мне приходит в голову, что я не знаю, что на самом деле там делают скобки. Я хочу использовать их только для группировки, но на самом ли деле это порождение? (Как я могу сказать?) Если так, есть ли способ сгруппировать условия, не вызывая подоболочки?

Wildcard
источник
Я полагаю, что я мог бы также использовать булеву логику для определения эквивалентного выражения, if ! [ -f file1 ] || ! [ -f file 2 ] || ! [ -f file3 ] ; thenно я хотел бы получить более общий ответ.
Подстановочный
Вы также можете отрицать внутри фигурных скобок, напримерif [[ ! -f file1 ]] && [[ ! -f file2 ]]; then
DopeGhoti
1
Да, я только что проверил, if [ ! 1 -eq 2 ] && [ ! 2 -eq 3 ]; then echo yep; fiи это работает. Я просто всегда пишу тесты с двойными скобками по привычке. Кроме того, чтобы убедиться, что это не так bash, я дополнительно проверил, if /bin/test ! 1 -eq 2 && /bin/test ! 2 -eq 3 ; then echo yep; fiи это работает так же.
DopeGhoti
1
@Wildcard - удалит [ ! -e file/. ] && [ -r file ]каталоги. отрицать это, как вам нравится. конечно, это то, что -dделает.
mikeserv
1
Смежный вопрос (похоже, но не так много обсуждений и ответы не очень полезны): unix.stackexchange.com/q/156885/135943
Wildcard

Ответы:

32

Вам нужно использовать { list;}вместо (list):

if ! { [ -f file1 ] && [ -f file2 ] && [ -f file3 ]; }; then
  : do something
fi

Оба они являются командами группирования , но { list;}выполняют команды в текущей среде оболочки.

Обратите внимание, что для разделения списка от обратного слова требуется ;in , вы также можете использовать другой разделитель. Пробел (или другой разделитель) после также требуется.{ list;}}{

cuonglm
источник
Спасибо! Поначалу меня что-то сбило с толку (поскольку я использую фигурные скобки awkчаще, чем в bash), это необходимость в пробелах после открытой фигурной скобки. Вы упоминаете «вы можете использовать и другой разделитель»; какие-нибудь примеры?
Подстановочный
@Wildcard: Да, пробел после открытой фигурной скобки необходим. Примером другого разделителя является символ новой строки, поскольку вы часто хотите, чтобы вы определили функцию.
Cuonglm
@don_crissti: В zsh, вы можете использовать пробелы
cuonglm
@don_crissti: &это также разделитель, который вы можете использовать { echo 1;:&}, также можно использовать несколько символов новой строки.
Cuonglm
2
Вы можете использовать любой метасимвол цитату из ручного they must be separated from list by whitespace or another shell metacharacter.Баша они: metacharacter: | & ; ( ) < > space tab . Для этой конкретной задачи я считаю, что любой из них & ; ) space tabбудет работать. .... .... Из zsh (как комментарий) each sublist is terminated by &', &!', or a newline.
8

Вы можете полностью использовать testфункциональность, чтобы достичь того, что вы хотите. Со страницы руководства test:

 ! expression  True if expression is false.
 expression1 -a expression2
               True if both expression1 and expression2 are true.
 expression1 -o expression2
               True if either expression1 or expression2 are true.
 (expression)  True if expression is true.

Таким образом, ваше состояние может выглядеть так:

if [ -f file1 -a -f file2 -a -f file3 ] ; then
    # do stuff with the files
fi

Для отрицания используйте экранированные скобки:

if [ ! \( -f file1 -a -f file2 -a -f file3 \) ] ; then
    echo "Error: You done goofed."
    exit 1
fi
# do stuff with the files
woodengod
источник
6

Чтобы переносить отрицание сложного условного выражения в оболочке, вы должны либо применить закон Де Моргана, и полностью отложить отрицание внутри [вызовов ...

if [ ! -f file1 ] || [ ! -f file2 ] || [ ! -f file3 ]
then
    # do stuff
fi

... или вы должны использовать then :; else...

if [ -f file1 ] && [ -f file2 ] && [ -f file3 ]
then :
else
  # do stuff
fi

if ! commandнедоступно и не доступно [[.

Если вам не нужна полная переносимость, не пишите сценарий оболочки . Вы на самом деле более вероятно найдете /usr/bin/perlв случайно выбранном Unix, чем вы bash.

zwol
источник
1
!это POSIX, который в настоящее время достаточно портативен. Даже системы, которые все еще поставляются с оболочкой Bourne, также имеют POSIX sh где-то еще в файловой системе, которую вы можете использовать для интерпретации вашего стандартного синтаксиса.
Стефан Шазелас
@ StéphaneChazelas Это технически верно, но по моему опыту проще переписать скрипт на Perl, чем справиться с трудностями поиска и активации POSIX-совместимой среды оболочки, начиная с /bin/sh. Сценарии Autoconf делают то, что вы предлагаете, но я думаю, что мы все знаем, как мало это одобрения.
Звол
5

другие заметили группировку {составных команд ;}, но если вы выполняете идентичные тесты на наборе, вы можете использовать другой вид:

if  ! for f in file1 file2 file3
      do  [ -f "$f" ] || ! break
      done
then  : do stuff
fi

... как показано в другом месте { :;}, нет проблем с вложением сложных команд ...

Обратите внимание, что выше (как правило) тестирует для обычных файлов. Если вы ищете только для существующих , читаемых файлов , которые не являются каталогами:

if  ! for f in file1 file2 file3
      do  [ ! -d "$f" ] && 
          [   -r "$f" ] || ! break
      done
then  : do stuff
fi

Если вам все равно, являются ли они каталогами или нет:

if  ! command <file1 <file2 <file3
then  : do stuff
fi

... работает для любого читаемого , доступного файла, но, вероятно, зависнет для писателей fifos w / out.

mikeserv
источник
2

Это не полный ответ на ваш главный вопрос, но я заметил, что вы упоминаете комплексное тестирование (читаемый файл) в комментарии; например,

if [ -f file1 ] && [ -r file1 ] && [ -f file2 ] && [ -r file2 ] && [ -f file3 ] && [ -r file3 ]

Вы можете немного закрепить это, определив функцию оболочки; например,

readable_file()
{
    [ -f "$1" ]  &&  [ -r "$1" ]
}

Добавить обработку ошибок (например, [ $# = 1 ]) по вкусу. Первое ifутверждение, приведенное выше, теперь может быть сжато до

if readable_file file1  &&  readable_file file2  &&  readable_file file3

и вы можете сократить это еще больше, сократив имя функции. Аналогично, вы можете определить not_readable_file()(или nrfдля краткости) и включить отрицание в функцию.

G-Man говорит: «Восстанови Монику»
источник
Хорошо, но почему бы просто не добавить цикл? readable_files() { [ $# -eq 0 ] && return 1 ; for file in "$@" ; do [ -f "$file" ] && [ -r "$file" ] || return 1 ; done ; } (Отказ от ответственности: я проверил этот код, и он работает, но он не был однострочным. Я добавил точки с запятой для публикации здесь, но не проверял их размещение.)
Wildcard
1
Хорошая точка зрения. Я хотел бы выразить небольшую озабоченность в связи с тем, что в функции скрывается больше логики управления (соединения); кто-то, кто читает сценарий и видит if readable_files file1 file2 file3, не будет знать , выполняла ли функция AND или OR - хотя (a) я думаю, что это довольно интуитивно понятно , (b) если вы не распространяете сценарий, это достаточно хорошо, если вы понимаете его и (c) поскольку я предложил сократить имя функции до неясности, я не в состоянии говорить о читабельности. … (Продолжение)
G-Man говорит «Восстановить Монику»
1
(Продолжение) ... Кстати, функция хороша, как вы ее написали, но вы можете заменить for file in "$@" ; doна for file do- по умолчанию in "$@", и (несколько нелогично) эта функция ;не только не нужна, но и не рекомендуется.
G-Man говорит: «Восстановите Монику»
0

Вы также можете отменить тесты фигурных скобок, чтобы повторно использовать исходный код:

if [[ ! -f file1 ]] || [[ ! -f file2 ]] || [[ ! -f file3 ]] ; then
  # do stuff with the files
fi
DopeGhoti
источник
2
Вам нужно ||вместо&&
cuonglm
Тест не "нет ли там ни одного файла", тест - " нет там ни одного файла". Использование ||будет выполнять предикат, если какой-либо из файлов отсутствует, а не тогда и только тогда, когда все файлы отсутствуют. НЕ (А И В И С) - это то же самое, что (НЕ А) И (НЕ В) И (НЕ С); увидеть оригинальный вопрос.
DopeGhoti
@ DopeGhoti, cuonglm прав. Это если нет (все файлы там), поэтому, когда вы разбиваете его таким образом, вам нужно ||вместо &&. Смотрите мой первый комментарий ниже самого моего вопроса.
Подстановочный
2
@ DopeGhoti: так not (a and b)же, как (not a) or (not b). См en.wikipedia.org/wiki/De_Morgan%27s_laws
cuonglm
1
Это должно быть четверг. Я никогда не мог овладеть четвергами. Исправлено, и я оставлю свою ногу в рот для потомков.
DopeGhoti
-1

Я хочу использовать их только для группировки, но на самом ли деле это порождение? (Как я могу сказать?)

Да, команды внутри (...)выполняются в подоболочке.

Чтобы проверить, есть ли у вас подоболочка, вы можете сравнить PID вашей текущей оболочки с PID интерактивной оболочки.

echo $BASHPID; (echo $BASHPID); 

Пример вывода, демонстрирующий, что скобки порождают подоболочку, а фигурные скобки не:

$ echo $BASHPID; (echo $BASHPID); { echo $BASHPID;}
50827
50843
50827
$ 
Дэвид Кинг
источник
6
()порождает недоразвитие .. возможно, вы имели в виду {}....
Heemayl
@heemayl, это другая часть моего вопроса - есть ли какой-то способ, которым я могу видеть или демонстрировать на моем экране факт появления подоболочки? (Это действительно может быть отдельный вопрос, но было бы полезно узнать здесь.)
Wildcard
1
@heemayl Ты прав! Я сделал, echo $$ && ( echo $$ )чтобы сравнить PID, и они были одинаковыми, но как раз перед тем, как я отправил комментарий о том, что вы ошиблись, я попробовал еще одну вещь. echo $BASHPID && ( echo $BASHPID )дает разные PID
Дэвид Кинг
1
@heemayl echo $BASHPID && { echo $BASHPID; }дать тот же PID, который вы предложили бы в этом случае. Спасибо за образование
Дэвид Кинг
@DavidKing, спасибо за использование тестовых команд $BASHPID, это еще одна вещь, которую мне не хватало.
Подстановочный