Skip to content

Latest commit

 

History

History
239 lines (152 loc) · 60.1 KB

Условия в скриптах bash (условные операторы).md

File metadata and controls

239 lines (152 loc) · 60.1 KB

Условия в скриптах bash (условные операторы)

Вступление

В 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 (краткое объяснение)

Основной синтаксис оператора 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, выполняются, только если оба условия ложны.

Основные правила условий

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

  1. Всегда оставляйте пробелы между скобками и фактической проверкой/сравнением. Следующие не будут работать:

    if [$foo -ge 3]; then

    Баш будет жаловаться на “отсутствуoщий ']'”.

  2. Всегда заканчивайте строку перед введением нового ключевого слова, такого как «then». Слова if, then, else, elif и fi являются ключевыми словами оболочки, что означает, что они не могут использовать одну и ту же строку. Поместите «;» между предыдущим оператором и ключевым словом или поместите ключевое слово в начале новой строки. Bash будет выдавать ошибки, такие как «ошибка синтаксиса, рядом с неожиданным токеном «fi», если вы этого не сделаете.

  3. Это хорошая привычка заключать в кавычки строковые переменные, если вы используете их в условиях, потому что в противном случае они могут создать проблемы, если они содержат пробелы и/или символы новой строки. Цитируя я имею в виду:

    if [ "$stringvar" == "tux" ]; then

    Есть несколько случаев, в которых вы не должны экранировать ковычками, но они редки. Вы увидите одну из них далее в руководстве.

Кроме того, есть две вещи, которые может быть полезно знать:

  1. Вы можете инвертировать условие, поставив перед ним «!». Пример:

    if [ ! -f regularfile ]; then

    Обязательно поместите "!" В скобки!

  2. Вы можете комбинировать условия с помощью определенных операторов. Для синтаксиса с одной скобкой, который мы использовали до сих пор, вы можете использовать «-a» для and и «-o» для or. Пример:

    if [ $foo -ge 3 -a $foo -lt 10 ]; then

    Приведенное выше условие вернет true, если $foo содержит целое число, большее или равное 3 и меньшее (Less Than) 10. Подробнее об этих выражениях объединения можно прочитать в соответствующих синтаксисах условий.

И еще одна базовая вещь: не забывайте, что условия могут также использоваться в других операторах, таких как while и until. Объяснение этого не входит в этот урок, но вы можете прочитать о них в Руководстве по Bash для начинающих.

Во всяком случае, я пока показал вам только условия в одинарных скобках. Однако есть и другие синтаксисы, о которых вы узнаете в следующем разделе.

Различные синтаксисы условий

Bash имеет различные синтаксисы для условий. Я перечислю три из них:

1. Синтаксис с одной скобкой

Это синтаксис условия, который вы уже видели в предыдущих параграфах; это самый старый поддерживаемый синтаксис. Он поддерживает три типа условий:

  • Файловые условия

    • Позволяет различные виды проверок. Пример:

      if [ -L symboliclink ]; then

      Приведенное выше условие верно, если файл символическая ссылка существует и является символической ссылкой. Дополнительные условия для файлов см. в таблице ниже.

  • Строковые условия

    • Позволяет проверять строку и сравнивать строки. Пример первый:

      if [ -z "$emptystring" ]; then

      Вышеуказанное условие истинно, если $emptystring является пустой строкой или неинициализированной переменной. Пример два:

      if [ "$stringvar1" == "cheese" ]; then

      Приведенное выше условие истинно, если $stringvar1 содержит только строку «cheese». Дополнительные условия на основе строк см. в таблице ниже.

  • Арифметические (числовые) условия

    • Позволяет сравнивать целые числа. Пример:

      if [ $num -lt 1 ]; then

      Приведенное выше условие возвращает true, если $num меньше 1. Более подробные арифметические условия см. в таблице ниже.

2. Синтаксис в двойных скобках

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

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» будет оцениваться перед «||» или «-о».

  • Пятое отличие состоит в том, что синтаксис в двойных скобках позволяет сопоставлять шаблоны с помощью оператора «=~». Смотрите таблица для получения дополнительной информации.

3. Синтаксис с двойными скобками

Существует также другой синтаксис для арифметических (основанных на числах) условий, наиболее вероятно взятый из оболочки 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
dd if=floppy.img of=/dev/fd0 # Write an image to a floppy
fi

[ -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
cat raw.wav > /dev/dsp # This actually works for certain raw wav files
fi

[ -d directory ] file ‘directory’ exists and is a directory. In UNIX-style, directories are a special kind of file.

if [ -d ~/.kde ]; then
echo “You seem to be a kde user.”
fi

[ -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
source ~/.bashrc
fi

[ -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
echo “Created files are inheriting the group ‘$(ls -ld . | awk ‘{ print $4 }’)’ from the working directory.”
fi

[ -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
chgrp $(id -g) file # Change the group if it’s not the effective one
fi

[ -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
echo “Warning! Anyone can delete and/or rename your files in /tmp!”
fi

[ -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
cp $file tmp.tmp # Make sure we’ll be able to read
file=”tmp.tmp”    # the file as many times as we like
fi

[ -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
mysql –socket=/var/lib/mysql/mysql.sock #See this MySQL tip
fi

[ -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
echo -e “nHello there. Message from terminal $(tty) to you.” > /dev/pts/3 #Anyone using that terminal will actually see this message!
fi

[ -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
echo “Running program executable as user $(ls -l executable | awk ‘{ print $3 }’).”
fi

[ -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
echo “You can view the contents of the /root directory.”
fi

[ 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=1
while (( swaps > 0 )); do

swaps=0
for (( i=0; i < (( ${#array[@]} – 1 )) ; i++ )); do
if [ “${array[$i]}” > “${array[$(( i + 1 ))]}” ]; then # Here is the sorting condition
tempstring=${array[$i]}
array[$i]=${array[$(( i + 1 ))]}
array[$(( i + 1 ))]=$tempstring
(( swaps=swaps + 1 ))
fi
done
done
echo ${array[@]} # Returns “blog linux tutorial”

[ 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
userinput=parse($userinput) # Only parse if the user actually gave some input.
fi

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
uninitializedvar=”initialized” # -z returns true on an uninitialized variable, so we initialize it here.
fi

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
echo “$email contains a valid e-mail address.”
fi

 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
echo “Previous command ran succesfully.”
fi

if [ $(ps -p $pid -o ni=) -ne $(nice) ]; then
echo “Process $pid is running with a non-default nice value”
fi

if [ $num -lt 0 ]; then
echo “Negative numbers not allowed; exiting…”
exit 1
fi

[ 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
echo “Shell option checkwinsize is disabled; enabling it so you can resize you terminal window without problems.”
shopt -s checkwinsize # This shell option is modifiable
fi

if [ -o login_shell ]; then
echo “This a a login shell.” # This shell option is not modifiable

fi

С помощью синтаксиса с двойными круглыми скобками вы можете использовать следующие условия:

5. Double-parenthesis syntax conditions:

Condition True if Example/explanation
(( NUM1 == 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 (( $? == 0 )); then # $? returns the exit status of the previous command
echo “Previous command ran succesfully.”
fi

if (( $(ps -p $pid -o ni=) != $(nice) )); then
echo “Process $pid is running with a non-default nice value”
fi

if (( $num < 0 )); then
echo “Negative numbers not allowed; exiting…”
exit 1
fi

(( NUM1 != NUM2 )) NUM1 is not equal to NUM2.
(( NUM1 > NUM2 )) NUM1 is greater than NUM2.
(( NUM1 >= NUM2 )) NUM1 is greater than or equal to NUM2.
(( NUM1 < NUM2 )) NUM1 is less than NUM2.
(( NUM1 <= NUM2 )) NUM1 is less than or equal to NUM2.

После этой сухой информационной нагрузки, здесь есть небольшое объяснение для тех, кто хочет узнать больше ...

Погружение немного глубже

Я сказал, что расскажу больше о том, что 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.


bash