Глава 7. Проверка условий
практически любой язык программирования включает в себя условные операторы, предназначенные для проверки условий, чтобы выбрать тот или иной путь развития событий в зависимости от этих условий. В Bash, для проверки условий, имеется команда test, различного вида скобочные операторы и условный оператор if/then.
7.1. Конструкции проверки условий
Оператор if/then проверяет — является ли код завершения списка команд 0 (поскольку 0 означает «успех» ), и если это так, то выполняет одну, или более, команд, следующие за словом then.
Существует специальная команда — [ (левая квадратная скобка). Она является синонимом команды test, и является встроенной командой (т.е. более эффективной, в смысле производительности). Эта команда воспринимает свои аргументы как выражение сравнения или как файловую проверку и возвращает код завершения в соответствии с результатами проверки (0 — истина, 1 — ложь).
Начиная с версии 2.02, Bash предоставляет в распоряжение программиста конструкцию [[ . ]] расширенный вариант команды test , которая выполняет сравнение способом более знакомым программистам, пишущим на других языках программирования. Обратите внимание: [[ — это зарезервированное слово, а не команда.
Bash исполняет [[ $a -lt $b ]] как один элемент, который имеет код возврата.
Круглые скобки (( . )) и предложение let . так же возвращают код 0 , если результатом арифметического выражения является ненулевое значение. Таким образом, арифметические выражения могут учавствовать в операциях сравнения.
Условный оператор if проверяет код завершения любой команды, а не только результат выражения, заключенного в квадратные скобки.
Оператор if/then допускает наличие вложенных проверок.
Это детальное описание конструкции «if-test» любезно предоставлено Stephane Chazelas.
Пример 7-1. Что есть «истина»?
Упражнение. Объясните результаты, полученные в Пример 7-1.
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. /.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. | |
[ -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 | |
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 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 | |
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. “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 $pid -o ni=) -ne $(nice) ]; then 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. | ||
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 С помощью синтаксиса с двойными круглыми скобками вы можете использовать следующие условия: 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 if (( $(ps -p $pid -o ni=) != $(nice) )); then if (( $num NUM2 )) | NUM1 is greater than NUM2. |
(( NUM1 >= NUM2 )) | NUM1 is greater than or equal to NUM2. | ||
(( NUM1 Погружение немного глубже Я сказал, что расскажу больше о том, что if по существу проверяет состояние завершения команд. И я так и сделаю. Основное правило bash, когда дело доходит до условий: 0 равно true, >0 равно false. Это в значительной степени противоположно многим языкам программирования, где 0 равно false, а 1 (или более) равно true. Причиной этого является то, что оболочки типа bash часто работают с программами. По соглашению UNIX программы используют состояние выхода для указания того, что выполнение прошло нормально или произошла ошибка. Поскольку успешное выполнение не требует каких-либо объяснений, ему нужен только один статус выхода. Однако, если возникла проблема, полезно знать, что пошло не так. Следовательно, 0 используется для успешного выполнения, а 1-255 для указания, какая ошибка произошла. Значения чисел 1-255 различаются в зависимости от программы, возвращающей их. В любом случае, if выполняет блок после then, когда команда возвращает 0. Да, условия являются командами. Фраза [$ foo -ge 3 ] возвращает статус завершения, а также два других синтаксиса! Следовательно, есть удобный прием, который вы можете использовать для быстрого тестирования состояния: В этом примере «echo true» выполняется только в том случае, если [$ foo -ge 3 ] возвращает 0 (true). Почему это так, спросите вы? Это потому, что bash оценивает состояние только при необходимости. При использовании комбинирующего выражения and оба условия должны быть истинными, чтобы комбинированное выражение возвращало true. Если первое условие возвращает false, не имеет значения, что возвращает второе; результат будет ложным. Следовательно, bash не оценивает второе условие, и именно поэтому в этом примере «echo true» не выполняется. То же самое относится к оператору or («||»), где второе условие не оценивается, если первое условие истинно. Источник |