Перевірте, чи є у глобуса відповідність у баші


223

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

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

( test -e glob*не вдається, якщо глобус відповідає більше ніж одному файлу.)


3
Я підозрюю, що моя відповідь нижче "явно правильна" таким чином, що всі інші подібні. Це однолінійний вбудований оболонку, який існував назавжди і, здається, "призначений інструмент для цієї конкретної роботи". Мене хвилює, що тут користувачі помилково посилаються на прийняту відповідь. Будь-хто, будь ласка, не соромтеся виправити мене, і я відкликаю тут свій коментар, я більше ніж радий помилитися і навчитися цьому. Якби різниця не виглядала настільки різкою, я б не ставив цього питання.
Брайан Крісман

1
Мої улюблені рішення цього питання - це команда find, яка працює в будь-якій оболонці (навіть без оболонок Борна), але вимагає GNU find, і команда compgen, яка явно башизм. Шкода, що я не можу прийняти обох відповідей.
Кен Блум

Примітка. Це питання було відредаговано з моменту його запитання. Оригінальна назва була "Перевірте, чи є у глобуса якісь матчі в баші". Конкретна оболонка, "bash", була відхилена від запитання після того, як я опублікував свою відповідь. Редагування заголовка питання робить мою відповідь помилковою. Я сподіваюся, що хтось може змінити або принаймні вирішити цю зміну.
Брайан Крісман

Відповіді:


178

Рішення для Баша :

compgen -G "<glob-pattern>"

Уникніть шаблону, інакше він буде попередньо розгорнутий на відповідність.

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

  • 1 за невідповідність,
  • 0 за 'одне або кілька матчів'

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

ОНОВЛЕННЯ : Запрошено використання прикладу.

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

9
Зауважте, що compgenце вбудована команда, що відрізняється від bash, і не є частиною вбудованих команд, встановлених стандартною оболонкою 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
Clay Bridges

Детальніше про те 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

Для кращої портативності та гнучкішого глобалізації використовуйте пошук:

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*і це також виключає можливість переповнення розширеної командну рядки (деякі оболонки мають межа 4K довжиною).

Якщо у вас виявляється, що надмірний вміст файлів, а кількість файлів, яка може збігатися, мала, скористайтеся 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оскільки ці варіанти як-то конфліктують.
Calimo

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]}". В іншому випадку, у вас вже є глобальне розширення в змінній масиві, тому ви gtg для передачі його іншим речам у вигляді списку, а не для того, щоб повторно розгорнути глобус.
Пітер Кордес

Приємно. Ви можете протестувати 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 /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; не впевнений, як знайти "простіше", ніж stat.
flabdablet

3
Будьте в курсі, що перенаправлення - це башізм, і тихо зробить неправильну справу в інших оболонках.
flabdablet

Це здається кращим, ніж відповідь flabdablet, findоскільки він приймає шляхи в глобальній кулі, і він більш короткий (не вимагає -maxdepthтощо). Це також здається кращим, ніж його statвідповідь, оскільки він не продовжує робити додаткові результати statу кожному додатковому поєдинку. Буду вдячний, якщо хтось може зробити внесок у кутові випадки, коли це не працює.
drwatsoncode

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

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

4

У мене є ще одне рішення:

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

Це прекрасно працює для мене. Чи є якісь кутові випадки, які я сумую?


2
Працює, за винятком випадків, коли файл насправді називається "glob *".
Ян Келінг

працює для передачі в глобусі як змінної - дає помилку "занадто багато аргументів", коли є більше одного збігу. "$ (echo $ GLOB)" не повертає жодного рядка або, принаймні, не інтерпретується як єдиний сингл, таким чином, помилка занадто багато аргументів
DKebler

@DKebler: його слід інтерпретувати як один рядок, оскільки він загорнутий у подвійні лапки.
користувач1934428

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]}" ]

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


Чому, з набором nullglob і масивом, можливо, порожнім, ви ще не можете протестувати if [ -e "${scripts[0]}" ]...? Ви також допускаєте можливість встановлення іменника набору параметрів оболонки ?
johnraff

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

1

Ця гидота, здається, працює:

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

Це, мабуть, вимагає баш, не ш.

Це працює, тому що опція nullglob змушує глобус оцінити порожній рядок, якщо немає відповідностей. Таким чином, будь-який не порожній вихід із команди echo вказує на те, що глобус щось відповідає.


Вам слід скористатисяif [ "`echo *py`" != "*py"]
yegle

1
Це не спрацювало б правильно, якби був названий файл *py.
Райан Ч. Томпсон,

Якщо файл не закінчується py, `echo *py`буде оцінено *py.
крик

1
Так, але це також зробиться, якщо є один файл *py, який називається , що є неправильним результатом.
Райан Ч. Томпсон,

Виправте мене, якщо я помиляюся, але якщо немає файлу, який відповідає *py, ваш сценарій буде лунати "Glob match"?
крик

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, використовуючи параметр nullglobshell.
Кен Блум

@Ken: Хм, я б не назвав nullglobхитрощі. Порівнювати єдиний результат з початковим шаблоном - це хитрості (і схильні до помилкових результатів), використовуючи nullglobне.
Кріс Джонсен

@Chris: Я думаю, що ви неправильно прочитали. Я не називав nullglobхитрощі.
Кен Блум

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

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

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

-1

ls | grep -q "glob.*"

Це не найефективніше рішення (якщо в каталозі є тонна файлів, це може бути повільним), але воно просте, легке для читання, а також має перевагу в тому, що регулярні виразки є більш потужними, ніж звичайні шаблони bash glob.


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

1
Для кращої відповіді спробуйте додати пояснення до свого коду.
MR
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.