Перевірте, чи є файли, що відповідають шаблону для виконання сценарію


29

Я намагаюся написати ifзаяву, щоб перевірити, чи є файли, що відповідають певній схемі. Якщо в каталозі є текстовий файл, він повинен запускати заданий сценарій.

Мій код на даний момент:

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

Будь ласка, дайте кілька ідей; Я хочу запустити скрипт, лише якщо .txtв каталозі є.


3
Ви впевнені, що "каталог" повинен бути /? Також раніше ви пропускаєте крапку з комою fi.
декап

Найчистішим надійним рішенням є використання, 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серед яких не приховані файли txt, compgen -G "*.txt" > /dev/null 2>&1здається, настільки ж швидко set -- *.txt; [ "$#" -gt 0 ](20,5 секунди для обох, коли повторювалося 10000 разів у моєму випадку).
Stéphane Chazelas

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 для будь-якого символу, поверне помилковим, якщо жодних символів не знайдено.
  • && ./script: Виконати ./scriptлише в тому випадку, якщо попередня команда була успішною ( &&)

2
findповертає false, якщо у нього проблеми з пошуком файлів, а не якщо він не знаходить жодного файлу. Ви хочете передати висновок, grep -q .щоб перевірити, чи знайде він щось.
Stéphane Chazelas

@StephaneChazelas ви, звичайно, абсолютно праві. Дивно, проте я перевірив це і, здавалося, працює. Мабуть, зробили щось дивне, тому що цього більше немає. Коли ви знайдете "проблеми з пошуку файлів"?
тердон

@terdon, як, наприклад, коли деякий каталог недоступний, або помилки вводу / виводу або помилка, повернена будь-яким системним викликом, який він робить. У такому випадку спробуйте після chmod a-x ..
Стефан Шазелас

8

Можливе рішення - також Bash вбудований compgen. Ця команда повертає всі можливі збіги для глобального шаблону та має вихідний код, який вказує, чи відповідають файли.

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

Я знайшов це питання, шукаючи швидкіші рішення.


1
Гарна знахідка! Якщо ви знаходитесь у багатобайтовому локалі, ви можете трохи покращити його LC_ALL=C compgen -G "*.txt" > /dev/null.
Stéphane Chazelas

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

Цей підхід використовує ||&& оператори та bash. Це оператори "або" і "і".

Отже, якщо команда stat повертає a $? дорівнює 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

Це питання широко висвітлено в стартовому потоці:


1
Навіщо використовувати нестандартний, statколи ls -dможна зробити те саме?
Stéphane Chazelas

Я думав, 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, як у GNU stat. Те, що ненульові команди статусу виходу повертаються до відмови, є специфічним для системи, мало сенсу робити їх припущення.
Стефан Шазелас

4

Як зазначає Chazelas, ваш сценарій не вдасться, якщо розширення wildcard відповідає більш ніж одному файлу.

Однак є хитрість, яку я використовую ( навіть мені це не дуже подобається ), щоб обійти:

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

Як це працює?

Розширення підстановки відповідатиме масиву імен файлів, ми отримуємо перше, якщо їх є, інакше нульове, якщо не відповідає.


ІМО, це найменш погана відповідь. Вони всі здаються досить жахливими, як ніби в мові відсутня основна риса.
підключення

@plugwash це навмисно ... * сценарії оболонки nix мають деякий базовий контроль потоку та кілька інших шансів і закінчень, але наприкінці дня його завдання полягає в склеюванні інших команд. Якщо bash смокче ... це тому, що команди, які ви використовуєте з нього,
смоктають

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для того, щоб побачити, чи щось сталося , аналогічно неправильно направлено
трійчатка

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...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.