Проверьте, существует ли файл с подстановочным знаком в сценарии оболочки [дубликат]


этот вопрос уже есть ответ здесь:

Я пытаюсь проверить, существует ли файл, но с подстановочным знаком. Вот мой пример:

if [ -f "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

Я также пробовал без двойных кавычек.

21 229

21 ответ:

самым простым должно быть полагаться на ls возвращаемое значение (оно возвращает ненулевое значение, когда файлы не существуют):

if ls /path/to/your/files* 1> /dev/null 2>&1; then
    echo "files do exist"
else
    echo "files do not exist"
fi

передал ls выход, чтобы сделать его абсолютно бесшумен.


EDIT: поскольку этот ответ получил немного внимания (и очень полезные замечания критика в качестве комментариев), вот оптимизация, которая также опирается на расширение glob, но избегает использования ls:

for f in /path/to/your/files*; do

    ## Check if the glob gets expanded to existing files.
    ## If not, f here will be exactly the pattern above
    ## and the exists test will evaluate to false.
    [ -e "$f" ] && echo "files do exist" || echo "files do not exist"

    ## This is all we needed to know, so we can break after the first iteration
    break
done

Это очень похоже на ответ @grok12, но это позволяет избежать ненужной итерации по всему списку.

Если ваша оболочка имеет nullglob опция, и она включена, шаблон подстановочных знаков, который не соответствует ни одному файлу, будет полностью удален из командной строки. Это сделает ls не смотрите аргументы пути, перечислите содержимое текущего каталога и преуспейте, что неверно. GNU стат, который всегда терпит неудачу, если не заданы аргументы или аргумент, называющий несуществующий файл, будет более надежным. Кроме того, оператор &> redirection является bashism.

if stat --printf='' /path/to/your/files* 2>/dev/null
then
    echo found
else
    echo not found
fi

еще лучше GNU найти, который может обрабатывать подстановочный поиск внутри и выходить, как только он находит один соответствующий файл, а не тратить время на обработку потенциально огромного списка из них, расширенного оболочкой; это также позволяет избежать риска того, что оболочка может переполнить буфер командной строки.

if test -n "$(find /dir/to/search -maxdepth 1 -name 'files*' -print -quit)"
then
    echo found
else
    echo not found
fi

не-GNU версии найти может не иметь - maxdepth здесь сделай найти искать только в /dir / to / search вместо того, чтобы все дерево каталогов коренится там.

вот мой ответ -

files=(xorg-x11-fonts*)

if [ -e "${files[0]}" ];
then
    printf "BLAH"
fi
for i in xorg-x11-fonts*; do
  if [ -f "$i" ]; then printf "BLAH"; fi
done

Это будет работать с несколькими файлами и с пробелами в именах файлов.

обновление:

хорошо, теперь у меня определенно есть решение:

files=$(ls xorg-x11-fonts* 2> /dev/null | wc -l)
if [ "$files" != "0" ]
then
   echo "Exists"
else
    echo "None found."
fi

> Exists

может быть, это поможет кому-то:

if [ "`echo xorg-x11-fonts*`" != "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

вы можете сделать следующее:

set -- xorg-x11-fonts*
if [ -f "" ]; then
    printf "BLAH"
fi

это работает с sh и производными: ksh и bash. Он не создает никакой суб-оболочки. $(..)и.` .."команды создают суб-оболочку: они разветвляют процесс, и они неэффективны. Конечно, он работает с несколькими файлами, и это решение может быть самым быстрым или вторым по скорости.

Он тоже работает, когда нет совпадений. Нет необходимости использовать nullglob, как говорит один из комментаторов. $1 будет содержать оригинал имя теста, поэтому тест-f $1 не будет успешным, потому что файл $1 не существует.

вопрос не был специфичен для Linux / Bash, поэтому я подумал, что добавлю способ Powershell, который обрабатывает подстановочные знаки по-разному-вы ставите его на котировки так, как показано ниже:

If (Test-Path "./output/test-pdf-docx/Text-Book-Part-I*"){
  Remove-Item -force -v -path ./output/test-pdf-docx/*.pdf
  Remove-Item -force -v -path ./output/test-pdf-docx/*.docx
}

Я думаю, что это полезно, потому что концепция исходного вопроса охватывает "оболочки" в целом не только Bash или Linux, а также будет применяться к пользователям Powershell с тем же вопросом.

строго говоря, если вы только хотите напечатать " бла " вот решение:

find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit

вот еще один способ :

doesFirstFileExist(){
    test -e ""
}

if doesFirstFileExist xorg-x11-fonts*
then printf "BLAH"
fi

но я думаю, что наиболее оптимальным является следующее, потому что он не будет пытаться сортировать имена файлов :

if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ]
then printf "BLAH"
fi

код bash, который я использую

if ls /syslog/*.log > /dev/null 2>&1; then 
   echo "Log files are present in /syslog/; 
fi

спасибо!

вот решение для конкретных проблема, которая не требует for петли или внешние команды как ls,find и тому подобное.

if [ "$(echo xorg-x11-fonts*)" != "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

как вы можете видеть, это просто немного сложнее, чем то, на что вы надеялись, и полагается на то, что если оболочка не может расширить глобус, это означает, что никаких файлов с этим глобусом не существует и echo выведет Глоб как, что позволяет нам сделать простое сравнение строк с проверьте, существует ли вообще какой-либо из этих файлов.

если бы мы обобщили процедуру однако, мы должны принять во внимание тот факт, что файлы может содержать пробелы в пределах их имен и / или путей и что глобус char может по праву расширяться до нуля (в вашем примере это было бы в случае файла, имя которого ровно настройка xorg-Х11-шрифты).

это может быть достигнуто с помощью следующей функции, в Баш.

function doesAnyFileExist {
   local arg="$*"
   local files=($arg)
   [ ${#files[@]} -gt 1 ] || [ ${#files[@]} -eq 1 ] && [ -e "${files[0]}" ]
}

возвращаясь к вашему примеру, он может быть вызван следующим образом.

if doesAnyFileExist "xorg-x11-fonts*"; then
    printf "BLAH"
fi

расширение Glob должно происходить внутри самой функции, чтобы она работала правильно, поэтому я помещаю аргумент в кавычки, и для этого есть первая строка в теле функции: так что любые несколько аргументов (которые могут быть результатом расширения glob вне функции, а также паразитного параметра) будут объединены в один. Другой подход может заключаться в том, чтобы вызвать ошибку, если есть более одного аргумента, еще один может игнорировать все, кроме 1-го аргумента.

вторая строка в теле функции определяет files ВАР к массив состоит из всех имен файлов, которые глобус расширился до, по одному для каждого элемента массива. это нормально, если имена файлов содержат пробелы, каждый элемент массива будет содержать имена как, включая пробелы.

в третья строка в теле функции делает две вещи:

  1. он сначала проверяет, есть ли более одного элемента в массиве. Если так, то это означает Глоб конечно расширился до чего-то (из-за того, что мы сделали на 1-й строке), что, в свою очередь, означает, что существует хотя бы один файл, соответствующий глобусу, и это все, что мы хотели знать.

  2. если на шаге 1. мы обнаружили, что получили менее 2 элементов в массиве, затем мы проверяем, есть ли он у нас, и если да, то мы проверяем, существует ли он, обычным способом. Нам нужно сделать эту дополнительную проверку, чтобы учесть аргументы функции без Глоб символов, в этом случае массив содержит только один, нерасширенные элемент.

Я использую этот:

filescount=`ls xorg-x11-fonts* | awk 'END { print NR }'`  
if [ $filescount -gt 0 ]; then  
    blah  
fi

ИМХО лучше использовать find всегда при тестировании файлов, глобусов или каталогов. Камнем преткновения при этом является find'S состояние выхода: 0 если все пути были пройдены успешно, >0 в противном случае. Выражение, которое вы передали find не создает эхо в своем коде выхода.

следующий пример проверяет, есть ли в каталоге записи:

$ mkdir A
$ touch A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty'
not empty

, когда A нет файлы grep не удается:

$ rm A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . || echo 'empty'
empty

, когда A не существует grep снова не удается, потому что find только печатает в stderr:

$ rmdir A
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty' || echo 'empty'
find: 'A': No such file or directory
empty

заменить -not -empty любой другой find выражение, но будьте осторожны, если вы -exec команда, которая выводит на stdout. В таких случаях может потребоваться grep для более конкретного выражения.

этот подход хорошо работает в шелл-скриптах. Первоначально вопрос состоял в том, чтобы искать Глоб xorg-x11-fonts*:

if find -maxdepth 0 -name 'xorg-x11-fonts*' -print | head -n1 | grep -q .
then
    : the glob matched
else
    : ...not
fi

обратите внимание, что else-разветвленный достигается, если xorg-x11-fonts* не совпадают, или find обнаружена ошибка. Для различения случая используйте $?.

if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi

попробуй такое

fileTarget="xorg-x11-fonts*"

filesFound=$(ls $fileTarget)  # 2014-04-03 edit 2: removed dbl-qts around $(...)

edit 2014-04-03 (удалены dbl-кавычки и добавлен тестовый файл 'Charlie 22.html '(2 пробела)

case ${filesFound} in
  "" ) printf "NO files found for target=${fileTarget}\n" ;;
   * ) printf "FileTarget Files found=${filesFound}\n" ;;
esac 

тест

fileTarget="*.html"  # where I have some html docs in the current dir

FileTarget Files found=Baby21.html
baby22.html
charlie  22.html
charlie21.html
charlie22.html
charlie23.html

fileTarget="xorg-x11-fonts*"

NO files found for target=xorg-x11-fonts*

это работает только в текущем каталоге, или где var fileTarget включает путь, который вы хотите проверить.

как о

if ls -l  | grep -q 'xorg-x11-fonts.*' # grep needs a regex, not a shell glob
then
     # do something
else
     # do something else
fi 

Если в сетевой папке есть огромное количество файлов, использование подстановочного знака сомнительно (скорость или переполнение аргументов командной строки).

Я закончил с:

if [ -n "$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then
  echo Such file exists
fi

вы также можете вырезать другие файлы

if [ -e $( echo  | cut -d" " -f1 ) ] ; then
   ...
fi

использование новых причудливых функций shmancy в оболочках ksh, bash и zsh (этот пример не обрабатывает пробелы в именах файлов):

# Declare a regular array (-A will declare an associative array. Kewl!)
declare -a myarray=( /mydir/tmp*.txt )
array_length=${#myarray[@]}

# Not found if the 1st element of the array is the unexpanded string
# (ie, if it contains a "*")
if [[ ${myarray[0]} =~ [*] ]] ; then
   echo "No files not found"
elif [ $array_length -eq 1 ] ; then
   echo "File was found"
else
   echo "Files were found"
fi

for myfile in ${myarray[@]}
do
  echo "$myfile"
done

Да, это пахнет, как Perl. Рад, что я не вмешался в это;)

нашел пару аккуратных решений, которыми стоит поделиться. Первый все еще страдает от проблемы" это сломается, если слишком много матчей":

pat="yourpattern*" matches=($pat) ; [[ "$matches" != "$pat" ]] && echo "found"

(Напомним, что если вы используете массив без [ ] синтаксис, вы получаете первый элемент массива.)

если у вас есть" shopt-s nullglob " в вашем скрипте, вы можете просто сделать:

matches=(yourpattern*) ; [[ "$matches" ]] && echo "found"

теперь, если можно иметь тонну файлов в каталоге, вы довольно хорошо застряли с использованием найти:

find /path/to/dir -maxdepth 1 -type f -name 'yourpattern*' | grep -q '.' && echo 'found'

man test

if [ -e file ]; then
...  
fi

будет работать для dir\file.

в отношении