В Bash имеется множество встроенных проверок и сравнений, что очень удобно во многих ситуациях. Вы, наверное, видели, если такие заявления раньше:
if [ $foo -ge 3 ]; then
Условие в этом примере по сути является командой. Это может звучать странно, но сравнение с квадратными скобками аналогично использованию встроенной команды test
, например:
if test $foo -ge 3; then
Если $foo больше(G) или равно(E) 3, блок после «then» будет выполнен. Если вы всегда задавались вопросом, почему bash использует -ge
или -eq
вместо >= или ==, это потому, что этот тип условия исходит из команды, где -ge
и -eq
- параметры.
И это то, что по сути, проверяет состояние завершения команды. Я объясню это более подробно далее в руководстве.
Также есть встроенные проверки, которые более специфичны для оболочек.
if [ -f regularfile ]; then
Вышеуказанное условие выполняется, если файл «regularfile» существует и это обычный файл. Обычный файл означает, что это не блок или символьное устройство или каталог. Таким образом, вы можете быть уверены, что Файл существует, прежде чем что-то делать с ним. Вы даже можете проверить, если файл читабелен!
if [ -r readablefile]; then
Приведенное выше условие выполняется, если файл «readablefile» существует и доступен для чтения. Легко, не правда ли?
Основной синтаксис оператора if … then выглядит следующим образом:
if <condition>; then
<commands>
fi
Условие, в зависимости от его типа, окружено определенным скобки, например. []. Вы можете прочитать о различных типах дальше в учебнике. Вы можете добавить команды, которые будут выполняться, когда условие ложно, с помощью ключевого слова else и использовать ключевое слово elif (elseif) для выполнения команд с другим условием, если основное условие ложно. Ключевое слово else всегда стоит последним. Пример:
if [ -r somefile ]; then
content=$(cat somefile)
elif [ -f somefile ]; then
echo "The file 'somefile' exists but is not readable to the script."
else
echo "The file 'somefile' does not exist."
fi
Краткое объяснение примера: сначала мы проверяем, является ли файл somefile читаемым («if [-r somefile ]»). Если так, мы читаем это в переменную. Если нет, мы проверяем, существует ли он на самом деле («elif [-f somefile ]»). Если это правда, мы сообщаем, что он существует, но не читается (если бы это было так, мы бы прочитали содержимое). Если файл не существует, мы также сообщаем об этом. Условие в elif выполняется только в том случае, если условие в if было ложным. Команды, принадлежащие else, выполняются, только если оба условия ложны.
Когда вы начинаете писать и использовать свои собственные условия, вы должны знать некоторые правила, чтобы избежать ошибок, которые трудно отследить. Вот три важных:
-
Всегда оставляйте пробелы между скобками и фактической проверкой/сравнением. Следующие не будут работать:
if [$foo -ge 3]; then
Баш будет жаловаться на “отсутствуoщий ']'”.
-
Всегда заканчивайте строку перед введением нового ключевого слова, такого как «then». Слова if, then, else, elif и fi являются ключевыми словами оболочки, что означает, что они не могут использовать одну и ту же строку. Поместите «;» между предыдущим оператором и ключевым словом или поместите ключевое слово в начале новой строки. Bash будет выдавать ошибки, такие как «ошибка синтаксиса, рядом с неожиданным токеном «fi», если вы этого не сделаете.
-
Это хорошая привычка заключать в кавычки строковые переменные, если вы используете их в условиях, потому что в противном случае они могут создать проблемы, если они содержат пробелы и/или символы новой строки. Цитируя я имею в виду:
if [ "$stringvar" == "tux" ]; then
Есть несколько случаев, в которых вы не должны экранировать ковычками, но они редки. Вы увидите одну из них далее в руководстве.
Кроме того, есть две вещи, которые может быть полезно знать:
-
Вы можете инвертировать условие, поставив перед ним «!». Пример:
if [ ! -f regularfile ]; then
Обязательно поместите "!" В скобки!
-
Вы можете комбинировать условия с помощью определенных операторов. Для синтаксиса с одной скобкой, который мы использовали до сих пор, вы можете использовать «-a» для and и «-o» для or. Пример:
if [ $foo -ge 3 -a $foo -lt 10 ]; then
Приведенное выше условие вернет true, если $foo содержит целое число, большее или равное 3 и меньшее (Less Than) 10. Подробнее об этих выражениях объединения можно прочитать в соответствующих синтаксисах условий.
И еще одна базовая вещь: не забывайте, что условия могут также использоваться в других операторах, таких как while и until. Объяснение этого не входит в этот урок, но вы можете прочитать о них в Руководстве по Bash для начинающих.
Во всяком случае, я пока показал вам только условия в одинарных скобках. Однако есть и другие синтаксисы, о которых вы узнаете в следующем разделе.
Bash имеет различные синтаксисы для условий. Я перечислю три из них:
Это синтаксис условия, который вы уже видели в предыдущих параграфах; это самый старый поддерживаемый синтаксис. Он поддерживает три типа условий:
-
Файловые условия
-
Позволяет различные виды проверок. Пример:
if [ -L symboliclink ]; then
Приведенное выше условие верно, если файл символическая ссылка существует и является символической ссылкой. Дополнительные условия для файлов см. в таблице ниже.
-
-
Строковые условия
-
Позволяет проверять строку и сравнивать строки. Пример первый:
if [ -z "$emptystring" ]; then
Вышеуказанное условие истинно, если $emptystring является пустой строкой или неинициализированной переменной. Пример два:
if [ "$stringvar1" == "cheese" ]; then
Приведенное выше условие истинно, если $stringvar1 содержит только строку «cheese». Дополнительные условия на основе строк см. в таблице ниже.
-
-
Арифметические (числовые) условия
-
Позволяет сравнивать целые числа. Пример:
if [ $num -lt 1 ]; then
Приведенное выше условие возвращает true, если $num меньше 1. Более подробные арифметические условия см. в таблице ниже.
-
Возможно, вы уже столкнулись с условиями, заключенными в двойные квадратные скобки, которые выглядят так:
if [[ "$stringvar" == *string* ]]; then
Синтаксис двойной скобки служит расширенной версией синтаксиса одной скобки; он в основном имеет те же особенности, но и некоторые важные различия с ним. Я перечислю их здесь:
-
Первое отличие_ можно увидеть в приведенном выше примере; при сравнении строк в синтаксисе двойных скобок используется глобализация оболочки(shell globbing). Это означает, что звездочка («*») расширится буквально до чего угодно, как вы, вероятно, знаете из обычного использования командной строки. Поэтому, если $stringvar где-либо содержит фразу «string», условие вернет true. Допускаются и другие формы срыва оболочки. Если вы хотите сопоставить и строку «String», и строку «string», вы можете использовать следующий синтаксис:
if [[ "$stringvar" == *[sS]tring* ]]; then
Обратите внимание, что допускается только общее глобирование оболочки. Такие Bash-специфичные вещи, такие как {1..4} или {foo, bar}, не будут работать. Также обратите внимание, что глобирование не будет работать, если вы экранировали кавычками правую строку. В этом случае вы должны оставить его без кавычек.
-
Второе отличие - это то, что разделение слов предотвращено. Следовательно, вы можете опустить размещение кавычек вокруг строковых переменных и без проблем использовать условие, подобное следующему:
if [[ $stringvarwithspaces != foo ]]; then
Тем не менее, цитирование строковых переменных остается хорошей привычкой, поэтому я рекомендую просто продолжать это делать.
-
Третье отличие состоит в том, что имена файлов не расширяются. Я проиллюстрирую это различие на двух примерах, начиная со старой ситуации с одной скобкой:
if [ -a *.sh ]; then
Вышеуказанное условие вернет true, если в рабочем каталоге есть один файл с расширением .sh. Если их нет, он вернет false. Если есть несколько файлов .sh, bash выдаст ошибку и прекратит выполнение скрипта. Это связано с тем, что *.sh распространяется на файлы в рабочем каталоге. Использование двойных скобок предотвращает это:
if [[ -a *.sh ]]; then
Приведенное выше условие вернет true, только если в рабочем каталоге есть файл с именем «*.sh», независимо от того, какие существуют другие файлы .sh. Звездочка взята буквально, потому что синтаксис в двойных скобках не расширяет имена файлов.
-
Четвертое отличие - это добавление более общеизвестных объединяющих выражений или, более конкретно, операторов «&&» и «||». Пример:
if [[ $num -eq 3 && "$stringvar" == foo ]]; then
Приведенное выше условие возвращает true, если $num равно 3, а $stringvar равно «foo». Также поддерживаются -a и -o, известные из синтаксиса с одной скобкой.
Обратите внимание, что оператор and имеет приоритет над оператором or, что означает, что «&&» или «-a» будет оцениваться перед «||» или «-о».
-
Пятое отличие состоит в том, что синтаксис в двойных скобках позволяет сопоставлять шаблоны с помощью оператора «=~». Смотрите таблица для получения дополнительной информации.
Существует также другой синтаксис для арифметических (основанных на числах) условий, наиболее вероятно взятый из оболочки Korn:
if (( $num <= 5 )); then
Приведенное выше условие выполняется, если $num меньше или равно 5. Этот синтаксис может показаться программистам более знакомым. Он включает в себя все "нормальные" операторы, такие как ==, <-> и =>. Он поддерживает комбинирующие выражения «&&» и «||» (но не выражения -a и -o!). Это эквивалентно встроенной команде let.
В следующей таблице перечислены возможные условия для синтаксиса с одинарными и двойными скобками. За исключением одного исключения, примеры приведены в синтаксисе с одинарными скобками, но всегда совместимы с двойными скобками.
1. File-based conditions: | ||
Condition | True if | Example/explanation |
---|---|---|
[ -a existingfile ] | file ‘existingfile’ exists. | if [ -a tmp.tmp ]; then rm -f tmp.tmp # Make sure we’re not bothered by an old temporary file fi |
[ -b blockspecialfile ] | file ‘blockspecialfile’ exists and is block special. | Block special files are special kernel files found in /dev, mainly used for ATA devices like hard disks, cd-roms and floppy disks.
if [ -b /dev/fd0 ]; then |
[ -c characterspecialfile ] | file ‘characterspecialfile’ exists and is character special. | Character special files are special kernel files found in /dev, used for all kinds of purposes (audio hardware, tty’s, but also /dev/null).
if [ -c /dev/dsp ]; then |
[ -d directory ] | file ‘directory’ exists and is a directory. | In UNIX-style, directories are a special kind of file.
if [ -d ~/.kde ]; then |
[ -e existingfile ] | file ‘existingfile’ exists. | (same as -a, see that entry for an example) |
[ -f regularfile ] | file ‘regularfile’ exists and is a regular file. | A regular file is neither a block or character special file nor a directory.
if [ -f ~/.bashrc ]; then |
[ -g sgidfile ] | file ‘sgidfile’ exists and is set-group-ID. | When the SGID-bit is set on a directory, all files created in that directory will inherit the group of the directory.
if [ -g . ]; then |
[ -G fileownedbyeffectivegroup ] | file ‘fileownedbyeffectivegroup’ exists and is owned by the effective group ID. | The effective group id is the primary group id of the executing user.
if [ ! -G file ]; then # An exclamation mark inverts the outcome of the condition following it |
[ -h symboliclink ] | file ‘symboliclink’ exists and is a symbolic link. | if [ -h $pathtofile ]; then pathtofile=$(readlink -e $pathtofile) #Make sure $pathtofile contains the actual file and not a symlink to it fi |
[ -k stickyfile ] | file ‘stickyfile’ exists and has its sticky bit set. | The sticky bit has got quite a history, but is now used to prevent world-writable directories from having their contents deletable by anyone.
if [ ! -k /tmp ]; then # An exclamation mark inverts the outcome of the condition following it |
[ -L symboliclink ] | file ‘symboliclink’ exists and is a symbolic link. | (same as -h, see that entry for an example) |
[ -N modifiedsincelastread ] | file ‘modifiedsincelastread’ exists and was modified after the last read. | if [ -N /etc/crontab ]; then killall -HUP crond # SIGHUP makes crond reread all crontabs fi |
[ -O fileownedbyeffectiveuser ] | file ‘fileownedbyeffectiveuser’ exists and is owned by the user executing the script. | if [ -O file ]; then chmod 600 file # Makes the file private, which is a bad idea if you don’t own it fi |
[ -p namedpipe ] | file ‘namedpipe’ exists and is a named pipe. | A named pipe is a file in /dev/fd/ that can be read just once. See my bash tutorial for a case in which it’s used.
if [ -p $file ]; then |
[ -r readablefile ] | file ‘readablefile’ exists and is readable to the script. | if [-r file ]; then content=$(cat file) # Set $content to the content of the file fi |
[ -s nonemptyfile ] | file ‘nonemptyfile’ exists and has a size of more than 0 bytes. | if [ -s logfile ]; then gzip logfile # Backup the old logfile touch logfile # before creating a fresh one. fi |
[ -S socket ] | file ‘socket’ exists and is a socket. | A socket file is used for inter-process communication, and features an interface similar to a network connection.
if [ -S /var/lib/mysql/mysql.sock ]; then |
[ -t openterminal ] | file descriptor ‘openterminal’ exists and refers to an open terminal. | Virtually everything is done using files on Linux/UNIX, and the terminal is no exception.
if [ -t /dev/pts/3 ]; then |
[ -u suidfile ] | file ‘suidfile’ exists and is set-user-ID. | Setting the suid-bit on a file causes execution of that file to be done with the credentials of the owner of the file, not of the executing user.
if [ -u executable ]; then |
[ -w writeablefile ] | file ‘writeablefile’ exists and is writeable to the script. | if [ -w /dev/hda ]; then grub-install /dev/hda fi |
[ -x executablefile ] | file ‘executablefile’ exists and is executable for the script. | Note that the execute permission on a directory means that it’s searchable (you can see which files it contains).
if [ -x /root ]; then |
[ newerfile -nt olderfile ] | file ‘newerfile’ was changed more recently than ‘olderfile’, or if ‘newerfile’ exists and ‘olderfile’ doesn’t. | if [ story.txt1 -nt story.txt ]; then echo “story.txt1 is newer than story.txt; I suggest continuing with the former.” fi |
[ olderfile -ot newerfile ] | file ‘olderfile’ was changed longer ago than ‘newerfile’, or if ‘newerfile’ exists and ‘olderfile’ doesn’t. | if [ /mnt/remote/remotefile -ot localfile ]; then cp -f localfile /mnt/remote/remotefile # Make sure the remote location has the newest version of the file, too fi |
[ same -ef file ] | file ‘same’ and file ‘file’ refer to the same device/inode number. | if [ /dev/cdrom -ef /dev/dvd ]; then echo “Your primary cd drive appears to read dvd’s, too.” fi |
2. String-based conditions: | ||
Condition | True if | Example/explanation |
[ STRING1 == STRING2 ] | STRING1 is equal to STRING2. | if [ “$1” == “moo” ]; then echo $cow # Ever tried executing ‘apt-get moo’? fiNote: you can also use a single “=” instead of a double one. |
[ STRING1 != STRING2 ] | STRING1 is not equal to STRING2. | if [ “$userinput” != “$password” ]; then echo “Access denied! Wrong password!” exit 1 # Stops script execution right here fi |
[ STRING1 > STRING2 ] | STRING1 sorts after STRING2 in the current locale (lexographically). | The backslash before the angle bracket is there because the bracket needs to be escaped to be interpreted correctly. As an example we have a basic bubble sort:
(Don’t feel ashamed if you don’t understand this, it is a more complex example) array=( linux tutorial blog ) swaps=0 |
[ STRING1 < STRING2 ] | STRING1 sorts before STRING2 in the current locale (lexographically). | |
[ -n NONEMPTYSTRING ] | NONEMPTYSTRING has a length of more than zero. | This condition only accepts valid strings, so be sure to quote anything you give to it.
if [ -n “$userinput” ]; then Note that you can also omit the “-n”, as brackets with just a string in it behave the same. |
[ -z EMPTYSTRING ] | EMPTYSTRING is an empty string. | This condition also accepts non-string input, like an uninitialized variable:
if [ -z $uninitializedvar ]; then |
Double-bracket syntax only: [[ STRING1 =~ REGEXPATTERN ]] |
STRING1 matches REGEXPATTERN. | If you are familiar with Regular Expressions, you can use this conditions to perform a regex match.
if [[ “$email” =~ “b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,4}b” ]]; then |
3. Arithmetic (number-based) conditions: | ||
Condition | True if | Example/explanation |
[ NUM1 -eq NUM2 ] | NUM1 is EQual to NUM2. | These conditions only accept integer numbers. Strings will be converted to integer numbers, if possible. Some random examples:
if [ $? -eq 0 ]; then # $? returns the exit status of the previous command if [ $(ps -p if [ $num -lt 0 ]; then |
[ NUM1 -ne NUM2 ] | NUM1 is Not Equal to NUM2. | |
[ NUM1 -gt NUM2 ] | NUM1 is Greater Than NUM2. | |
[ NUM1 -ge NUM2 ] | NUM1 is Greater than or Equal to NUM2. | |
[ NUM1 -lt NUM2 ] | NUM1 is Less Than NUM2. | |
[ NUM1 -le NUM2 ] | NUM1 is Less than or Equal to NUM2. | |
4. Miscellaneous conditions: | ||
Condition | True if | Example/explanation |
[ -o shelloption ] | shell option ‘shelloption’ is enabled. | Shell options modify the behaviour of bash, except a few unmodifiable ones that indicate the shell status.
if [ ! -o checkwinsize ] # An exclamation mark inverts the outcome of the condition following it if [ -o login_shell ]; then |
С помощью синтаксиса с двойными круглыми скобками вы можете использовать следующие условия:
После этой сухой информационной нагрузки, здесь есть небольшое объяснение для тех, кто хочет узнать больше ...
Я сказал, что расскажу больше о том, что if по существу проверяет состояние завершения команд. И я так и сделаю. Основное правило bash, когда дело доходит до условий: 0 равно true, >0 равно false. Это в значительной степени противоположно многим языкам программирования, где 0 равно false, а 1 (или более) равно true. Причиной этого является то, что оболочки типа bash часто работают с программами. По соглашению UNIX программы используют состояние выхода для указания того, что выполнение прошло нормально или произошла ошибка. Поскольку успешное выполнение не требует каких-либо объяснений, ему нужен только один статус выхода. Однако, если возникла проблема, полезно знать, что пошло не так. Следовательно, 0 используется для успешного выполнения, а 1-255 для указания, какая ошибка произошла. Значения чисел 1-255 различаются в зависимости от программы, возвращающей их.
В любом случае, if выполняет блок после then, когда команда возвращает 0. Да, условия являются командами. Фраза [$ foo -ge 3 ] возвращает статус завершения, а также два других синтаксиса! Следовательно, есть удобный прием, который вы можете использовать для быстрого тестирования состояния:
[ $foo -ge 3 ] && echo true
В этом примере «echo true» выполняется только в том случае, если [$ foo -ge 3 ]
возвращает 0 (true). Почему это так, спросите вы? Это потому, что bash оценивает состояние только при необходимости. При использовании комбинирующего выражения and оба условия должны быть истинными, чтобы комбинированное выражение возвращало true. Если первое условие возвращает false, не имеет значения, что возвращает второе; результат будет ложным. Следовательно, bash не оценивает второе условие, и именно поэтому в этом примере «echo true» не выполняется. То же самое относится к оператору or («||»), где второе условие не оценивается, если первое условие истинно.
Что ж, так много для погружения. Если вы хотите узнать еще больше, я хотел бы указать вам на Руководство по расширенному написанию сценариев и, возможно, Справочное руководство по Bash или даже это Руководство системного администратора по написанию сценариев Bash.