Проверьте, есть ли у шара какие-либо совпадения в bash


223

Если я хочу проверить наличие одного файла, я могу проверить его с помощью test -e filenameили [ -e filename ].

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

( test -e glob*не удается, если глобус соответствует более чем одному файлу.)


3
Я подозреваю, что мой ответ, приведенный ниже, «явно верен» таким образом, что все остальные вроде мошенничества. Это встроенная оболочка, состоящая из одной строки, которая существует всегда и кажется «предназначенным инструментом для этой конкретной работы». Я обеспокоен тем, что пользователи будут ошибочно ссылаться на принятый ответ здесь. Кто-нибудь, пожалуйста, не стесняйтесь поправлять меня, и я отзову свой комментарий здесь, я более чем счастлив быть неправым и учиться на этом. Если бы разница не выглядела так радикально, я бы не стал поднимать этот вопрос
Брайан Крисман

1
Мои любимые решения этого вопроса - команда find, которая работает в любой оболочке (даже не в оболочке Борна), но требует GNU find, и команда compgen, которая явно является Bashism. Жаль, что я не могу принять оба ответа.
Кен Блум

Примечание. Этот вопрос был отредактирован с момента его появления. Первоначальное название было «Проверить, есть ли у шара какие-либо совпадения в bash». Конкретная оболочка 'bash' была исключена из вопроса после того, как я опубликовал свой ответ. Редактирование названия вопроса делает мой ответ ошибочным. Я надеюсь, что кто-то может изменить или хотя бы исправить это изменение.
Брайан Крисман

Ответы:


178

Bash конкретное решение:

compgen -G "<glob-pattern>"

Побег шаблон или он будет предварительно расширен в спички.

Статус выхода:

  • 1 для несоответствия,
  • 0 за «один или несколько матчей»

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

ОБНОВЛЕНИЕ : пример использования запрашивается.

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi

9
Обратите внимание, что compgenэто специфичная для bash встроенная команда, которая не является частью стандартных встроенных команд оболочки Unix, определенных в POSIX. pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/… Поэтому не используйте его в сценариях, где переносимость на другие оболочки является проблемой.
Диомидис Спинеллис

1
Мне кажется, что подобный эффект без встроенных команд bash заключался бы в использовании любой другой команды, которая действует на глоб и не работает, если не найдено ни одного файла, например ls: if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi- может быть полезно для кода гольф? Сбой, если есть файл с именем, совпадающим с глобаном, которому глобус не должен был соответствовать, но в этом случае у вас, вероятно, большие проблемы.
Деви Морган

4
@DewiMorgan Это проще: if ls /tmp/*Files &> /dev/null; then echo exists; fi
Глиняные Мосты

Для получения дополнительной информации compgenсм. man bashИли сhelp compgen
el-teedee

2
да, процитируйте его, или подстановочный знак будет предварительно расширен. compgen "dir / *. ext"
Брайан Крисман

169

Параметр оболочки nullglob - это действительно башизм.

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

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

Для лучшей переносимости и большей гибкости используйте find:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

Явный действия -print -quit используются для поиска вместо неявного действия -print по умолчанию, поэтому поиск будет завершен, как только будет найден первый файл, соответствующий критериям поиска. В случае совпадения большого количества файлов это должно выполняться намного быстрее, чем echo glob*или, ls glob*и это также исключает возможность чрезмерного заполнения расширенной командной строки (некоторые оболочки имеют ограничение в 4 КБ).

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

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi

10
findкажется, совершенно правильно. У него нет угловых случаев, поскольку оболочка не выполняет расширение (и не передает нерасширенный глобус какой-либо другой команде), она переносима между оболочками (хотя, очевидно, не все параметры, которые вы используете, указаны в POSIX), и она быстрее, чем ls -d glob*(предыдущий принятый ответ), потому что он останавливается, когда достигает первого совпадения.
Кен Блум

1
Обратите внимание, что этот ответ может потребовать, так shopt -u failglobкак эти параметры могут как-то конфликтовать.
Калимо

findРешение будет соответствовать файлу без Глоб символов , а также. В этом случае это то, что я хотел. Просто кое-что знать, хотя.
Мы все Моника

1
Поскольку кто-то другой решил отредактировать мой ответ, чтобы он сказал это, по-видимому.
flabdablet

1
unix.stackexchange.com/questions/275637/… обсуждает, как заменить -maxdepthопцию для поиска POSIX.
Кен Блум

25
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi

2
Чтобы избежать возможного ложного «нет совпадений», установите nullglobвместо проверки, чтобы увидеть, равен ли один результат самому шаблону. Некоторые шаблоны могут соответствовать именам, которые в точности совпадают с самим шаблоном (например a*b, но неa?b или [a]).
Крис Джонсен

Я полагаю, что это очень маловероятно вероятностью того, что на самом деле есть файл с именем, похожим на глоб. (например, кто-то побежал touch '*py'), но это указывает мне другое хорошее направление.
Кен Блум

Мне нравится этот как самая общая версия.
Кен Блум

И тоже самое короткое. Если вы ожидаете только одно совпадение, вы можете использовать "$M"его для сокращения "${M[0]}". В противном случае, у вас уже есть расширение glob в переменной массива, так что вы gtg для передачи его другим вещам в виде списка, вместо того, чтобы заставлять их повторно расширять glob.
Питер Кордес

Ницца. Вы можете проверить M быстрее (меньше байтов и без запуска [процесса) сif [[ $M ]]; then ...
Tobia

22

мне нравиться

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

Это и удобочитаемо, и эффективно (если нет огромного количества файлов).
Основным недостатком является то, что это гораздо тоньше, чем кажется, и я иногда вынужден добавить длинный комментарий.
Если есть совпадение, "glob*"он расширяется оболочкой, и все совпадения передаются exists(), который проверяет первое и игнорирует остальные.
Если нет совпадений, "glob*"передаетсяexists() и обнаруживаются, что их там тоже не существует.

Изменить: может быть ложное срабатывание, см. Комментарий


13
Он может возвращать ложный положительный результат, если глобус является чем-то вроде *.[cC](может быть, не файл cили Cфайл, но файл называется *.[cC]) или ложным отрицательным, если первый файл, расширенный из этого, является, например, символической ссылкой на несуществующий файл или файл в каталог, к которому у вас нет доступа (вы хотите добавить || [ -L "$1" ]).
Стефан Шазелас

Интересный. Shellcheck сообщает, что глобирование работает только при -eсовпадении 0 или 1. Это не работает для нескольких матчей, потому что это станет [ -e file1 file2 ]и это не получится. Также см. Github.com/koalaman/shellcheck/wiki/SC2144 для обоснования и предлагаемых решений.
Томас Праксл

10

Если у вас есть набор globfail, вы можете использовать этот сумасшедший (что вы действительно не должны)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

или

q=( * ) && echo 0 || echo 1

2
Фантастическое использование провала. Никогда не должен использоваться ... но действительно красиво. :)
Брайан Крисман

Вы можете положить покупателя внутрь паренса. Таким образом, это влияет только на тест:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet

8

test -e имеет неудачное предупреждение о том, что битые символические ссылки не существуют. Так что вы можете проверить это тоже.

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi

4
Это все еще не исправляет ложноположительные значения в именах файлов, в которых есть глобальные специальные символы, как указывает Стефан Чазелас в ответе Дана Блоха. (если только вы не обезьяна с нульглобом).
Питер Кордес

3
Вы должны избегать -oи -aв test/ [. Например, здесь, он терпит неудачу , если $1это =в большинстве реализаций. Используйте [ -e "$1" ] || [ -L "$1" ]вместо этого.
Стефан Шазелас,

4

Чтобы несколько упростить ответ MYYN, основываясь на его идее:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi

4
Закрыть, но что, если вы совпадаете [a], есть файл с именем [a], но нет файла с именем a? Мне все еще нравится nullglobза это. Кто-то может расценить это как педантичный, но мы можем быть настолько же правы, насколько это разумно.
Крис Джонсен

@ sondra.kinsey Это неправильно; глобус [a]должен совпадать только aс буквальным именем файла [a].
tripleee

4

Исходя из ответа flabdablet , для меня кажется, что самым простым (не обязательно самым быстрым) будет просто использовать find , оставляя расширение glob на shell, например:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

Или ifкак:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi

Это работает точно так же, как версия, которую я уже представил, используя stat; не уверен, как найти "проще", чем стат.
flabdablet

3
Имейте в виду, что перенаправление &> является башизмом, и в других оболочках он будет действовать неправильно.
flabdablet

Это кажется лучше, чем findответ flabdablet, потому что он принимает пути в глобусе, и это более кратко (не требует и -maxdepthт.д.). Это также кажется лучше, чем его statответ, потому что он не продолжает делать дополнения statпри каждом дополнительном глобальном совпадении. Я был бы признателен, если бы кто-нибудь мог внести угловой случай, когда это не работает.
drwatsoncode

1
После дальнейшего рассмотрения я бы добавил, -maxdepth 0потому что это обеспечивает большую гибкость при добавлении условий. например, предположим, что я хочу ограничить результат только соответствующими файлами. Я мог бы попытаться find $glob -type f -quit, но это возвратило бы true, если глобус НЕ соответствовал файлу, но действительно совпадал с каталогом, который содержал файл (даже рекурсивно). Напротив find $glob -maxdepth 0 -type f -quit, вернул бы true, только если сам шарик соответствовал хотя бы одному файлу. Обратите внимание, что maxdepthэто не мешает глобу иметь компонент каталога. (К вашему сведению 2>достаточно. Не нужно &>)
drwatsoncode

2
Смысл использования findв первую очередь состоит в том, чтобы избежать генерации оболочкой и сортировки потенциально огромного списка совпадений глобуса; find -name ... -quitбудет соответствовать не более одного имени файла. Если сценарий полагается на передачу сгенерированного оболочкой списка совпадений glob find, вызов которого приводит только к findненужным накладным расходам при запуске процесса. Простое тестирование полученного списка непосредственно на предмет пустоты будет быстрее и понятнее.
flabdablet

4

У меня есть еще одно решение:

if [ "$(echo glob*)" != 'glob*' ]

Это хорошо работает для меня. Есть ли какие-то угловые случаи, по которым я скучаю?


2
Работает за исключением случаев, когда файл на самом деле называется 'glob *'.
Ян Келлинг

действительно работает для передачи glob как переменной - выдает ошибку «слишком много аргументов», когда есть более одного совпадения. «$ (echo $ GLOB)» не возвращает одну строку или, по крайней мере, она не интерпретируется как одна единственная, что приводит к ошибке слишком большого количества аргументов
DKebler

@DKebler: это следует интерпретировать как одну строку, потому что оно заключено в двойные кавычки.
user1934428

3

В Bash вы можете использовать массив; если глобус не совпадает, ваш массив будет содержать одну запись, которая не соответствует существующему файлу:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

Примечание: если вы nullglobустановили, scriptsбудет пустой массив, и вы должны проверить с [ "${scripts[*]}" ]или [ "${#scripts[*]}" != 0 ]вместо. Если вы пишете библиотеку, которая должна работать с или безnullglob , вы захотите

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

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


Почему при установленном nullglob и массиве, возможно, пустом, вы все еще не можете проверить if [ -e "${scripts[0]}" ]...? Вы также допускаете возможность установки набора опций оболочки ?
Джонрафф

@ johnraff, да, я обычно предполагаю, nounsetчто активен. Кроме того, может быть (немного) дешевле проверить, что строка не пуста, чем проверить наличие файла. Хотя вряд ли, учитывая, что мы только что выполнили глоб, означает, что содержимое каталога должно быть свежим в кеше ОС.
Тоби Спейт

1

Эта мерзость, кажется, работает:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

Это, вероятно, требует bash, а не sh.

Это работает, потому что опция nullglob заставляет глобус вычислять пустую строку, если совпадений нет. Таким образом, любой непустой вывод команды echo указывает, что глобус соответствует чему-либо.


Вы должны использоватьif [ "`echo *py`" != "*py"]
Yegle

1
Это не будет работать правильно, если есть файл с именем *py.
Райан К. Томпсон

Если нет конца файла py, `echo *py`будет оцениваться *py.
Yegle

1
Да, но это также будет сделано, если будет назван единственный файл *py, что является неправильным результатом.
Райан К. Томпсон

Поправьте меня, если я ошибаюсь, но если нет *pyподходящего файла , ваш скрипт будет отображать "Glob matched"?
Yegle

1

Я не видел этот ответ, поэтому я решил поставить его там:

set -- glob*
[ -f "$1" ] && echo "found $@"

1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

объяснение

Если нет совпадений для glob*, то $1будет содержать 'glob*'. Тест -f "$1"не будет верным, потому что glob*файл не существует.

Почему это лучше, чем альтернативы

Это работает с sh и производными: ksh и bash. Он не создает никаких вложенных оболочек. $(..)и `...`команды создают вложенную оболочку; они разворачивают процесс и поэтому работают медленнее, чем это решение.


1

Как это в (тестовые файлы, содержащие pattern):

shopt -s nullglob
compgen -W *pattern* &>/dev/null
case $? in
    0) echo "only one file match" ;;
    1) echo "more than one file match" ;;
    2) echo "no file match" ;;
esac

Это гораздо лучше, чем compgen -G: потому что мы можем различать больше случаев и более точно.

Может работать только с одним символом подстановки *


0
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

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


1
Это даст неправильный ответ, если шаблон [a]используется, когда файл [a]присутствует, а файл aотсутствует. Он скажет «найдено», хотя единственный файл, которому он должен соответствовать a, на самом деле отсутствует.
Крис Джонсен

Эта версия должна работать в обычном POSIX / bin / sh (без искажений), и в случае, если мне это нужно, глобус в любом случае не имеет скобок, и мне не нужно беспокоиться о случаях, которые ужасно патологический. Но я думаю, что нет единого способа проверить, соответствуют ли какие-либо файлы глобусу.
Кен Блум

0
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi

2
Эта версия завершается ошибкой, когда совпадает ровно один файл, но вы можете избежать использования FOUND = -1, используя nullglobопцию shell.
Кен Блум

@Ken: Хм, я бы не назвал nullglobэто клуджем. Сравнение одного результата с исходным шаблоном - это клочок (и склонность к ложным результатам), использование nullglob- нет.
Крис Джонсен

@ Крис: Я думаю, что вы неправильно прочитали. Я не назвал nullglobклудж.
Кен Блум

1
@Ken: Действительно, я неправильно прочитал. Пожалуйста, примите мои извинения за мою недействительную критику.
Крис Джонсен

-1
(ls glob* &>/dev/null && echo Files found) || echo No file found

5
Также будет возвращать false, если есть соответствующие каталоги, glob*и, например, у вас нет записи для перечисления этих каталогов.
Стефан Шазелас

-1

ls | grep -q "glob.*"

Не самое эффективное решение (если в каталоге много файлов, оно может быть медленным), но оно простое, легко читаемое и имеет то преимущество, что регулярные выражения являются более мощными, чем простые шаблоны bash glob.


-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true

1
Для лучшего ответа попробуйте добавить некоторые пояснения к вашему коду.
Масуд Рахими
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.