Проверьте, существуют ли файлы, соответствующие шаблону, чтобы выполнить скрипт

29

Я пытаюсь написать ifзаявление, чтобы проверить, есть ли какие-либо файлы, соответствующие определенному шаблону. Если в каталоге есть текстовый файл, он должен запустить данный скрипт.

Мой код в настоящее время:

if [ -f /*.txt ]; then ./script fi

Пожалуйста, дайте несколько идей; Я хочу запустить скрипт, только если .txtв каталоге есть.

user40952
источник
3
Вы уверены, что «каталог» должен быть /? Кроме того, вы пропустите точку с запятой раньше fi.
depquid
Чистейший надежное решение я столкнулся, чтобы использовать , findкак описано здесь , на StackOverflow .
Джошуа Голдберг

Ответы:

39
[ -f /*.txt ]

вернет true, только если есть один (и только один) не скрытый файл, в /имени которого заканчивается имя, .txtи если этот файл является обычным файлом или символической ссылкой на обычный файл.

Это связано с тем, что подстановочные знаки раскрываются оболочкой перед передачей в команду (здесь [).

Так что, если есть /a.txtи /b.txt, [будут переданы 5 аргументов: [, -f, /a.txt, /b.txtи ]. [потом жаловался бы, что -fдано слишком много аргументов.

Если вы хотите убедиться, что *.txtшаблон расширяется хотя бы до одного скрытого файла (обычного или нет):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobявляется bashконкретным, но скорлупа нравится ksh93, zsh, yash, tcshимеют эквивалентные высказывания.

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

Стандартный shэквивалент будет:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

Проблема в том, что с оболочками Bourne или POSIX, если шаблон не совпадает, он расширяется до самого себя. Так что, если *.txtрасширится до *.txt, вы не знаете, будет ли это потому, что в .txtкаталоге нет файла или потому что есть один названный файл *.txt. Использование [*].txt *.txtпозволяет различать два.

Стефан Шазелас
источник
[ -f /*.txt ]довольно быстро по сравнению с compgen.
Даниэль Бёмер,
@ DanielBöhmer [ -f /*.txt ]был бы неправильным, но в моем тесте для каталога, содержащего 3425файлы, 94которые не являются скрытыми текстовыми файлами, compgen -G "*.txt" > /dev/null 2>&1скорость оказалась равной set -- *.txt; [ "$#" -gt 0 ](20,5 секунд для обоих при повторении 10000 раз в моем случае).
Стефан Шазелас
11

Вы всегда можете использовать find:

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

Объяснение:

  • find . : поиск в текущем каталоге
  • -maxdepth 1: не искать подкаталоги
  • -type f : поиск только обычных файлов
  • name "*.txt" : поиск файлов, оканчивающихся на .txt
  • 2>/dev/null : перенаправить сообщения об ошибках в /dev/null
  • | grep -q . : grep для любого символа, вернет false, если символы не найдены.
  • && ./script: Выполнить, ./scriptтолько если предыдущая команда была успешной ( &&)
Тердон
источник
2
findвозвращает false только в том случае, если у него возникают проблемы с поиском файлов, но не в том случае, если файл не найден Вы хотите передать вывод, grep -q .чтобы проверить, если он что-то находит.
Стефан Шазелас
@ StephaneChazelas вы совершенно правы, конечно. Странно, хотя, я проверил это, и это казалось работающим. Должно быть, сделал что-то странное, потому что это уже не так. Когда найдете "есть проблемы с поиском файлов"?
Terdon
@terdon, например, когда какой-либо каталог недоступен, или ошибки ввода-вывода или любая ошибка, возвращаемая любым системным вызовом, который он делает. В этом случае попробуйте после chmod a-x ..
Стефан Шазелас
8

Возможное решение также встроено в Bash compgen. Эта команда возвращает все возможные совпадения для шаблона выделения и имеет код выхода, указывающий, совпадают ли какие-либо файлы.

compgen -G "/*.text" > /dev/null && ./script

Я нашел этот вопрос, когда искал решения, которые быстрее.

Даниэль Бёмер
источник
1
Хорошая находка! Если вы находитесь в многобайтовой локали, вы можете немного улучшить ее LC_ALL=C compgen -G "*.txt" > /dev/null.
Стефан Шазелас
7

Вот один лайнер, чтобы сделать это:

$ ls
file1.pl  file2.pl

файлы существуют

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

файлы не существуют

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

Этот подход использует ||и &&оператор в Баше. Это операторы «или» и «и».

Таким образом, если команда stat возвращает значение, $?равное 0, echoвызывается первая , если возвращается 1, то echoвызывается вторая .

вернуть результаты из статистики

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

Этот вопрос подробно рассматривается на стековом потоке:

SLM
источник
1
Зачем использовать нестандартные, statкогда ls -dможно сделать то же самое?
Стефан Шазелас
Я думал ls -dсписки каталог? Кажется, не работает, когда я просто попытался перечислить каталог с файлами, ls -d *.plнапример, в нем.
SLM
Вы можете заменить утверждение слева от &&, ls *.txtи оно также будет работать. Убедитесь, что вы отправили stdout и stderr в /dev/nullсоответствии с рекомендациями @slm.
unxnut
1
Если вы используете , ls *.txtи нет никаких файлов присутствуют в каталоге это будет возвращать $? = 2, который все равно будет работать с , если тогда, но это был один из моих причин для выбора statболее ls. Я хотел 0 для успеха и 1 для неудачи.
SLM
ls -dэто список каталогов вместо их содержимого. Так ls -dчто просто делает lstatна файл, как GNU statделает. То, что ненулевые команды статуса выхода возвращаются при сбое, зависит от системы, поэтому нет смысла делать на них предположения.
Стефан Шазелас
4

Как указывает Chazelas, ваш скрипт потерпит неудачу, если расширение по шаблону соответствует более чем одному файлу.

Тем не менее, есть хитрость, которую я использую ( даже если она мне не очень нравится ):

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

Как это работает?

Расширение с подстановочными знаками будет соответствовать массиву имен файлов, мы получим первое, если они есть, в противном случае - null, если совпадения нет.

Мистер пей
источник
ИМО это наименее плохой ответ здесь. Все они кажутся довольно ужасными, хотя, как будто отсутствует основная функция в языке.
plugwash
@plugwash это преднамеренно ... * Сценарии оболочки nix имеют некоторый базовый контроль потока и некоторые другие шансы и недостатки, но в конечном итоге задача состоит в том, чтобы склеить другие команды. Если bash отстой ... это потому, что команды, которые вы используете из него, отстой
cb88
2
Это неправильная логика (а вы пропускаете кавычки). Это проверяет, является ли первый соответствующий файл обычным файлом. Это могут быть нестандартные файлы, но может быть несколько других .txtфайлов, которые являются обычными . Попробуйте, например, после mkdir a.txt; mkfifo b.txt; echo regular > c.txt.
Стефан Шазелас
1

Просто как:

cnt=`ls \*.txt 2>/dev/null | wc -l`
if [ "$cnt" != "0" ]; then ./script fi

wc -l считает строки в расширенном шаблоне.

Ким Йоханссон
источник
1
Downvote: Это собирает удивительное количество начинающих антипаттернов в небольшом количестве кода. Вы не должны анализировать lsвывод и почти никогда не проверять $?напрямую, потому что это ifуже сделано. Кроме того, использование, wcчтобы увидеть, произошло ли что-то подобное, неправильно направлено.
tripleee
0

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

Вот альтернативная структура, которую я тестировал вчера:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

Значение выхода из grep, по-видимому, определяет путь через управляющую структуру. Это также тестирует с регулярными выражениями, а не с шаблонами оболочки. Некоторые из моих систем имеют команду «pcregrep», которая позволяет выполнять более сложные сопоставления регулярных выражений.

(Я отредактировал этот ответ, чтобы убрать «ls» в подстановке команд после прочтения вышеупомянутой критики за ее анализ.)

Чарли
источник
-2

если вы хотите использовать предложение if, оцените количество:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
Rusty75
источник