Тестировать несколько файловых условий одним махом (BASH)? - PullRequest
32 голосов
/ 02 августа 2011

Часто при написании для оболочки 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

-нет -f для вас ✓
IR ДА СЭР -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 `]'

➝ нет -wd для вас ✖
➝ нет -w |-d для вас ✖
➝ нет [-w [-d ..]] для вас ✖
➝ нет -w && -d для вас ✖

Чего мне здесь не хватает?

Ответы [ 6 ]

33 голосов
/ 02 августа 2011

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

MYFILE=/tmp/data.bin
if [ -f "$MYFILE"  -a  -r "$MYFILE"  -a  -w "$MYFILE" ]; then
    #do stuff
fi
unset MYFILE
21 голосов
/ 02 августа 2011

Конечно, вам нужно как-то использовать AND, как указали Керрек (+1) и Бен (+1).Вы можете сделать это несколькими способами.Вот результаты ала-микробенчмарка для нескольких методов:

Самый переносимый и читаемый способ:

$ 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

bashism, но читаемый и быстрый

$ time for i in $(seq 100000); do [[ 1 = 1 ]] && [[ 2 = 2 ]] && [[ 3 = 3 ]]; done
real    0m1.285s

bashism, но вполне читаемый и быстрый.

$ 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
8 голосов
/ 02 августа 2011

Вы хотите -a как в -f foo -a -d foo (на самом деле этот тест был бы ложным, но вы поняли).

Вы были близки с &, вам просто нужно && как в [ -f foo ] && [ -d foo ] хотя при этом запускается несколько команд, а не одна.

Вот страница руководства для теста , которая является командой, на которую [ является ссылкой.Современные реализации test имеют гораздо больше возможностей (наряду со встроенной в оболочку версией [[, которая описана на странице руководства вашей оболочки).

3 голосов
/ 09 декабря 2013
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
2 голосов
/ 25 мая 2016

Это похоже на работу (обратите внимание на двойные скобки):

#!/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
2 голосов
/ 14 августа 2011

Почему бы не написать функцию для этого?

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...