Ответ 1
Вы можете использовать логические операторы для множества условий, например. -a
для AND
:
MYFILE=/tmp/data.bin
if [ -f "$MYFILE" -a -r "$MYFILE" -a -w "$MYFILE" ]; then
#do stuff
fi
unset MYFILE
Часто при написании для оболочки bash необходимо проверить, существует ли (или не существует) файл (или каталог) и предпринимать соответствующие действия. Наиболее распространенными среди этих тестов являются...
-e
- файл существует, -f
- файл является обычным файлом (не каталогом или файлом устройства), -s
- файл не является нулевым размером, -d
- файл является каталогом, -r
- файл имеет разрешение на чтение, -w
- файл имеет запись или -x
разрешение на выполнение (для пользователя, выполняющего тест)
Это легко подтвердить, как показано в этом каталоге, доступном для пользователя....
#/bin/bash
if [ -f "/Library/Application Support" ]; then
echo 'YES SIR -f is fine'
else echo 'no -f for you'
fi
if [ -w "/Library/Application Support" ]; then
echo 'YES SIR -w is fine'
else echo 'no -w for you'
fi
if [ -d "/Library/Application Support" ]; then
echo 'YES SIR -d is fine'
else echo 'no -d for you'
fi
➝ no -f для вас ✓
➝ ДА SIR -w отлично ✓
➝ ДА SIR -d отлично ✓
Мой вопрос, хотя кажущийся очевидным и вряд ли невозможным, - это просто объединить эти тесты, не выполняя их отдельно для каждого условия... К сожалению...
if [ -wd "/Library/Application Support" ]
▶ -wd: unary operator expected
if [ -w | -d "/Library/Application Support" ]
▶ [: missing `]'
▶ -d: command not found
if [ -w [ -d "/Library.... ]] & if [ -w && -d "/Library.... ]
▶ [: missing `]'
➝ no -wd для вас ✖
➝ no -w | -d для вас ✖
➝ no [-w [-d..]] для вас ✖
➝ no -w && & -d для вас ✖
Что мне здесь не хватает?
Вы можете использовать логические операторы для множества условий, например. -a
для AND
:
MYFILE=/tmp/data.bin
if [ -f "$MYFILE" -a -r "$MYFILE" -a -w "$MYFILE" ]; then
#do stuff
fi
unset MYFILE
Конечно, вам нужно использовать AND так или иначе, как указал Керрек (+1) и Бен (+1). Вы можете сделать это несколькими путями. Вот результаты ala-microbenchmark для нескольких методов:
Самый переносимый и читаемый способ:
$ time for i in $(seq 100000); do [ 1 = 1 ] && [ 2 = 2 ] && [ 3 = 3 ]; done
real 0m2.583s
все еще переносимый, менее читаемый, быстрее:
$ time for i in $(seq 100000); do [ 1 = 1 -a 2 = 2 -a 3 = 3 ]; done
real 0m1.681s
но читаемый и быстрый
$ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
real 0m1.285s
но вполне читаемый и самый быстрый.
$ time for i in $(seq 100000); do [[ 1 = 1 && 2 = 2 && 3 = 3 ]]; done
real 0m0.934s
Обратите внимание, что в bash, "[" является встроенным, поэтому bash использует внутреннюю команду, а не символическую ссылку на /usr/bin/test exacutable. "[[" Является ключевым словом bash. Поэтому самый медленный способ:
time for i in $(seq 100000); do /usr/bin/\[ 1 = 1 ] && /usr/bin/\[ 2 = 2 ] && /usr/bin/\[ 3 = 3 ]; done
real 14m8.678s
Вы хотите -a
как в -f foo -a -d foo
(на самом деле этот тест будет ложным, но вы получите идею).
Вы были близки с помощью &
, вам просто понадобилось &&
, как в [ -f foo ] && [ -d foo ]
, хотя оно запускает несколько команд, а не один.
Вот справочная страница для теста, которая является командой, к которой привязана [
. У современных реализаций test
есть намного больше возможностей (наряду с версией с оболочкой [[
, которая задокументирована в вашей man-странице оболочки).
check-file(){
while [[ ${#} -gt 0 ]]; do
case $1 in
fxrsw) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" && -w "$2" ]] || return 1 ;;
fxrs) [[ -f "$2" && -x "$2" && -r "$2" && -s "$2" ]] || return 1 ;;
fxr) [[ -f "$2" && -x "$2" && -r "$2" ]] || return 1 ;;
fr) [[ -f "$2" && -r "$2" ]] || return 1 ;;
fx) [[ -f "$2" && -x "$2" ]] || return 1 ;;
fe) [[ -f "$2" && -e "$2" ]] || return 1 ;;
hf) [[ -h "$2" && -f "$2" ]] || return 1 ;;
*) [[ -e "$1" ]] || return 1 ;;
esac
shift
done
}
check-file fxr "/path/file" && echo "is valid"
check-file hf "/path/folder/symlink" || { echo "Fatal error cant validate symlink"; exit 1; }
check-file fe "file.txt" || touch "file.txt" && ln -s "${HOME}/file.txt" "/docs/file.txt" && check-file hf "/docs/file.txt" || exit 1
if check-file fxrsw "${HOME}"; then
echo "Your home is your home from the looks of it."
else
echo "You infected your own home."
fi
Почему бы не написать функцию для этого?
check_file () {
local FLAGS=$1
local PATH=$2
if [ -z "$PATH" ] ; then
if [ -z "$FLAGS" ] ; then
echo "check_file: must specify at least a path" >&2
exit 1
fi
PATH=$FLAGS
FLAGS=-e
fi
FLAGS=${FLAGS#-}
while [ -n "$FLAGS" ] ; do
local FLAG=`printf "%c" "$FLAGS"`
if [ ! -$FLAG $PATH ] ; then false; return; fi
FLAGS=${FLAGS#?}
done
true
}
Затем просто используйте его как:
for path in / /etc /etc/passwd /bin/bash
{
if check_file -dx $path ; then
echo "$path is a directory and executable"
else
echo "$path is not a directory or not executable"
fi
}
И вы должны получить:
/ is a directory and executable
/etc is a directory and executable
/etc/passwd is not a directory or not executable
/bin/bash is not a directory or not executable
Кажется, что это работает (обратите внимание на двойные скобки):
#!/bin/bash
if [[ -fwd "/Library/Application Support" ]]
then
echo 'YES SIR -f -w -d are fine'
else
echo 'no -f or -w or -d for you'
fi