Перетворити глобус на `знайти '


11

У мене знову і знову виникала ця проблема: у мене глобус, який відповідає точно правильним файлам, але викликає Command line too long. Кожен раз , коли я перетворив його в яку - то комбінацію findі grepщо роботи для конкретної ситуації, але не 100% еквівалентний.

Наприклад:

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

Чи є інструмент для перетворення глобусів у findвирази, про які я не знаю? Або є можливість findузгодити глобус, не збігаючись з тим самим глобулом у піддиректорі (наприклад foo/*.jpg, заборонено відповідати bar/foo/*.jpg)?


Розкрийте дужку, і ви зможете використовувати отримані вирази за допомогою -pathабо -ipath. find . -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg'повинен працювати - крім того, що це буде відповідати /fooz/blah/bar/quuxA/pic1234d.jpg. Це буде проблема?
муру

Так, це буде проблемою. Він повинен бути 100% еквівалентом.
Оле Танге

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

Я додав ваше розширення як відповідь на запитання. Я сподіваюся, що це не так вже й погано.
петерх

Хіба ви не можете echo <glob> | cat, якщо припустити, що я знаю баш, ехо є вбудованим, і, отже, не існує максимальної межі команд
Ferrybig

Відповіді:


15

Якщо проблема полягає в тому, що ви отримуєте аргумент-list-is-too long error, використовуйте цикл або вбудовану оболонку. У той час як command glob-that-matches-too-muchпомилка може вийти, for f in glob-that-matches-too-muchне так, ви можете просто зробити:

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done

Цикл може бути неприємно повільним, але він повинен працювати.

Або:

printf "%s\0" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg |
  xargs -r0 something

( printfбудучи вбудованим у більшість оболонок, вищезазначене працює навколо обмеження execve()системного виклику)

$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606

Також працює з bash. Я не впевнений, де саме це зафіксовано.


І Vim's, glob2regpat()і Python fnmatch.translate()можуть перетворювати глобуси в регулярні вирази, але обидва вони також використовують .*для *, узгоджуючи поперек /.


Якщо це вірно, то заміна somethingз echoповинен зробити це.
Оле Танге

1
@OleTange Ось чому я запропонував printf- це буде швидше, ніж дзвонити echoтисячі разів, і пропонує більшу гнучкість.
муру

4
Існує обмеження на аргументи, через які можна передати exec, що стосується зовнішніх команд, таких як cat; але це обмеження не стосується вбудованих оболонок таких команд, як printf.
Стівен Кітт

1
@OleTange Рядок не надто довгий, оскільки printfвін є вбудованим, і оболонки, ймовірно, використовують той же метод для подання аргументів до нього, який вони використовують для перерахування аргументів for. catне є вбудованим.
муру

1
Технічно є снаряди, як, mkshде printfне вбудований, і оболонки, як, ksh93де catє (або можуть бути) вбудовані. Дивіться також zargsв zshпрацювати навколо нього , без необхідності вдаватися до xargs.
Стефан Шазелас

9

find(для -name/ -pathстандартних предикатів) використовує шаблони підстановок так само, як глобуси (зверніть увагу, що {a,b}це не глобальний оператор; після розширення ви отримуєте два глобуси). Основна відмінність - поводження з косою рисою (а також крапки з файлами та брудом, за якими спеціально не обробляються find). *в глобусах не буде охоплено декілька каталогів. */*/*призведе до переліку до двох рівнів каталогів. Додавання -path './*/*/*'відповідатиме будь-яким файлам, що мають глибину щонайменше трьох рівнів, і не зупинятиметься findперелічувати вміст будь-якого каталогу на будь-якій глибині.

Для конкретного

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

кілька глобусів, це легко перекласти, ви хочете мати каталоги на глибині 3, щоб ви могли використовувати:

find . -mindepth 3 -maxdepth 3 \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

(або -depth 3з деякими findреалізаціями). Або POSIXly:

find . -path './*/*/*' -prune \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

Що гарантувало б, що ті *і ?не могли відповідати /персонажам.

( findвсупереч глобусам буде читати вміст каталогів, відмінних від foo*barпоточного в каталозі¹, а не сортувати список файлів. Але якщо ми залишимо осторонь, що те, що відповідає [A-Z]або поведінка */ ?щодо недійсних символів, - це не вказано, ви отримаєте той самий список файлів).

Але в будь-якому випадку, як показало @muru , не потрібно вдаватися, findякщо це лише для розбиття списку файлів на кілька запусків, щоб обійти межу execve()системного виклику. Деякі оболонки типу zshzargs) або ksh93command -x) навіть мають вбудовану підтримку для цього.

З zsh(чиї кульками також мають еквівалент -type fі більшість інших findпредикатів), наприклад:

autoload zargs # if not already in ~/.zshrc
zargs ./foo*bar/quux[A-Z](|.bak)/pic[0-9][0-9][0-9][0-9]?.jpg(.) -- cmd

( (|.bak)Є оператором Глоб всупереч {,.bak}, то (.)Глоб класифікатор є еквівалентом find«s -type f, додайте oNтуди , щоб пропустити сортування , як з find, Dщоб включити дот-файли (не відноситься до цього Glob))


¹ Щоб findсканувати дерево каталогів, як глобуси, вам знадобиться щось на зразок:

find . ! -name . \( \
  \( -path './*/*' -o -name 'foo*bar' -o -prune \) \
  -path './*/*/*' -prune -name 'pic[0-9][0-9][0-9][0-9]?.jpg' -exec cmd {} + -o \
  \( ! -path './*/*' -o -name 'quux[A-Z]' -o -name 'quux[A-Z].bak' -o -prune \) \)

Тобто обрізайте всі каталоги на рівні 1, крім foo*barтих, і всі на рівні 2, окрім quux[A-Z]або quux[A-Z].bak, та виберіть pic...їх на рівні 3 (та обріжте всі каталоги на цьому рівні).


3

Ви можете написати регулярний вираз для пошуку, який відповідає вашим вимогам:

find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'

Чи є інструмент, який робить це перетворення, щоб уникнути людських помилок?
Оле Танге

Ні, але тільки зміни я зробив було бігти ., додати додатковий матч за .bakі зміни *до [^/]*не збігаються шляху , як / Foo / Foo / бар і т.д.
sebasth

Але навіть ваше перетворення неправильне. ? не змінюється на [^ /]. Це саме та людська помилка, якої я хочу уникати.
Оле Танге

1
Я думаю, що з egrep ви можете скоротити [0-9][0-9][0-9][0-9]?до[0-9]{3,4}
wjandrea


0

Узагальнюючи замітку на іншій моїй відповіді , як прямий варіант відповіді на ваше запитання, ви можете використовувати цей shсценарій POSIX для перетворення глобуса у findвираз:

#! /bin/sh -
glob=${1#./}
shift
n=$#
p='./*'

while true; do
  case $glob in
    (*/*)
      set -- "$@" \( ! -path "$p" -o -path "$p/*" -o -name "${glob%%/*}" -o -prune \)
      glob=${glob#*/} p=$p/*;;
    (*)
      set -- "$@" -path "$p" -prune -name "$glob"
      while [ "$n" -gt 0 ]; do
        set -- "$@" "$1"
        shift
        n=$((n - 1))
      done
      break;;
  esac
done
find . "$@"

Для використання з одним стандартним shглобусом (так, не з двома глобусами вашого прикладу, який використовує розширення дужок ):

glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
  -type f -exec cmd {} +

(що не ігнорує dot-файли або dot-dirs, за винятком .і ..не сортує список файлів).

Цей працює лише з глобусами відносно поточного каталогу, без компонентів .або ..компонентів. Доклавши певних зусиль, ви можете розширити його на будь-який глобус, більше ніж на глобус ... Це також можна оптимізувати, щоб glob2find 'dir/*'не шукати dirте саме, що було б для шаблону.

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