Як я можу використовувати зворотні чи негативні підстановки під час відповідності шаблонів у оболонці unix / linux?


325

Скажіть, я хочу скопіювати вміст каталогу, виключаючи файли та папки, назви яких містять слово "Музика".

cp [exclude-matches] *Music* /target_directory

Що потрібно зробити замість [виключити-відповідників] для цього?

Відповіді:


375

У Bash ви можете зробити це, включивши extglobопцію, як це (замінити lsз cpі додати цільової каталог, звичайно)

~/foobar> shopt extglob
extglob        off
~/foobar> ls
abar  afoo  bbar  bfoo
~/foobar> ls !(b*)
-bash: !: event not found
~/foobar> shopt -s extglob  # Enables extglob
~/foobar> ls !(b*)
abar  afoo
~/foobar> ls !(a*)
bbar  bfoo
~/foobar> ls !(*foo)
abar  bbar

Пізніше ви можете відключити extglob за допомогою

shopt -u extglob

14
Мені подобається ця особливість:ls /dir/*/!(base*)
Ерік Робертсон

6
Як ви включаєте все ( ), а також виключаєте! (B )?
Ілля Лінн

4
Як би ви відповідали, скажімо, все, що починається f, крім foo?
Нолдорін

8
Чому це вимкнено за замовчуванням?
weberc2

3
shopt -o -u histexpand, якщо вам потрібно шукати файли з знаками оклику в них - за замовчуванням, extglob вимкнено за замовчуванням, щоб він не заважав histexpand, в документах пояснюється, чому це так. співставляйте все, що починається з f, крім foo: f! (oo), звичайно, «їжа» все одно збігатиметься (вам знадобиться f! (oo *), щоб зупинити речі, які починаються з «foo», або, якщо ви хочете позбутися ! певних речей , що закінчуються в використанні '.foo' ( ! .foo), або префіксом: myprefix ( .foo) (відповідає myprefixBLAH але не myprefixBLAH.foo)
osirisgothra

227

Параметр extglobоболонки дає більш потужне узгодження шаблону в командному рядку.

Ви вмикаєте його shopt -s extglobі вимикаєте shopt -u extglob.

У вашому прикладі ви б спочатку робили:

$ shopt -s extglob
$ cp !(*Music*) /target_directory

Повний доступний внутр закінчився GLOB оператори Бінг є (витяг з man bash):

Якщо параметр оболонки extglob увімкнено за допомогою вбудованого магазину, розпізнається кілька розширених операторів відповідності шаблону. Список шаблонів - це список одного або декількох шаблонів, розділених знаком |. Складені візерунки можуть бути сформовані з використанням одного або декількох з наступних підшаблонів:

  • ? (шаблон-список)
    Відповідає нулю або одному зустрічанню заданих шаблонів
  • * (список шаблонів)
    Відповідає нулю або більше входжень заданих шаблонів
  • + (перелік шаблонів)
    Збігає одне або більше входжень заданих шаблонів
  • @ (список шаблонів)
    Відповідає одному із заданих шаблонів
  • ! (список шаблонів)
    Відповідає будь-якому, крім однієї із заданих шаблонів

Так, наприклад, якщо ви хочете перерахувати всі файли в поточному каталозі, які не є, .cабо .hфайли, ви зробите:

$ ls -d !(*@(.c|.h))

Звичайно, нормальне обшивання оболонки працює, тому останній приклад також можна записати як:

$ ls -d !(*.[ch])

1
У чому причина -d?
Big McLargeHuge

2
@Koveras для випадку, коли один з файлів .cабо .h- це каталог.
tzot

@DaveKennedy Це перелік всього в поточному каталозі D, але не вміст підкаталогів, які можуть міститися в каталозі D.
шпора

23

Не в баші (що я знаю), але:

cp `ls | grep -v Music` /target_directory

Я знаю, що це не саме те, що ви шукали, але це вирішить ваш приклад.


За замовчуванням ls буде розміщувати кілька файлів на рядок, що, ймовірно, не дасть правильних результатів.
Даніель Бунгерт

10
Тільки коли stdout є терміналом. При використанні в конвеєрі ls друкує одне ім'я файлу на рядок.
Адам Розенфілд

ls ставить декілька файлів на рядок лише при виході на термінал. Спробуйте самі - "ls | less" ніколи не матиме декілька файлів у рядку.
SpoonMeiser

3
Він не працює для назви файлів, що містять пробіли (або інші символи білого відблиску).
tzot

7

Якщо ви хочете уникнути вартості пам’яті за допомогою команди exec, я вважаю, що ви можете зробити краще з xargs. Я думаю, що наступне є більш ефективною альтернативою

find foo -type f ! -name '*Music*' -exec cp {} bar \; # new proc for each exec



find . -maxdepth 1 -name '*Music*' -prune -o -print0 | xargs -0 -i cp {} dest/

6

У bash альтернативою shopt -s extglobє GLOBIGNOREзмінна . Насправді не краще, але мені легше запам'ятати.

Приклад, який може бути тим, що хотів оригінальний плакат:

GLOBIGNORE="*techno*"; cp *Music* /only_good_music/

Закінчивши, unset GLOBIGNOREмати змогу rm *techno*у вихідному каталозі.


5

Ви також можете використовувати досить простий forцикл:

for f in `find . -not -name "*Music*"`
do
    cp $f /target/dir
done

1
Це робить рекурсивну знахідку, яка відрізняється поведінкою, ніж те, чого хоче ОП.
Адам Розенфілд

1
використовувати -maxdepth 1для нерекурсивного?
автомат

Я знайшов це найчистішим рішенням без необхідності вмикати / вимикати параметри оболонки. Варіант -maxdepth рекомендується в цій публікації мати результат, необхідний ОП, але все залежить від того, що ви намагаєтеся досягти.
Девід Лапоант

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

5

Мої особисті переваги - використовувати grep та команду while. Це дозволяє писати потужні, але читабельні сценарії, що гарантує вам, що ви робите саме те, що хочете. Крім того, використовуючи команду echo, ви можете виконати сухий запуск перед виконанням фактичної операції. Наприклад:

ls | grep -v "Music" | while read filename
do
echo $filename
done

буде надрукувати файли, які ви закінчите копіювати. Якщо список правильний, наступним кроком буде просто замінити команду echo командою copy, як описано нижче:

ls | grep -v "Music" | while read filename
do
cp "$filename" /target_directory
done

1
Це буде працювати до тих пір, поки у ваших іменах файлів не буде вкладок, нових рядків, більше ніж один пробіл у рядку чи зворотних косих рисових рядків. Хоча це патологічні випадки, добре пам’ятати про можливість. У bashви можете використовувати while IFS='' read -r filename, але тоді нові рядки по - , як і раніше є проблемою. Взагалі найкраще не використовувати lsдля перерахування файлів; такі інструменти findнабагато краще підходять.
Thedward

Без додаткових інструментів:for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; done
Thedward

mywiki.wooledge.org/ParsingLs перераховує ряд додаткових причин, чому слід уникати цього.
трійка

5

Трюк я не бачив тут ще , що не використовується extglob, findабо grepє для лікування двох списків файлів як наборів та «диф» їх з допомогою comm:

comm -23 <(ls) <(ls *Music*)

commє кращим над diffтим, що він не має зайвої крихти.

Це повертає всі елементи множини 1 ls, які також не є у множині 2 ls *Music*,. Для коректної роботи обидва набори мають бути відсортовані. Немає проблем для lsглобального розширення, але якщо ви використовуєте щось подібне find, не забудьте звернутися до цього sort.

comm -23 <(find . | sort) <(find . | grep -i '.jpg' | sort)

Потенційно корисно.


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

Дуже вдалий момент, @MarkStosberg. Хоча, одним із переваг цієї методики є те, що ви можете прочитати виключення з фактичного файлу, наприкладcomm -23 <(ls) exclude_these.list
James M. Lay

3

Одне рішення для цього можна знайти із знахідкою.

$ mkdir foo bar
$ touch foo/a.txt foo/Music.txt
$ find foo -type f ! -name '*Music*' -exec cp {} bar \;
$ ls bar
a.txt

У Find є досить багато варіантів, ви можете отримати досить конкретний характер того, що ви включаєте та виключаєте.

Редагувати: Адам у коментарях зазначив, що це рекурсивно. знайти варіанти mindepth та maxdepth можуть бути корисними для управління цим.


Це робить рекурсивну копію, яка відрізняється поведінкою. Він також породжує новий процес для кожного файлу, який може бути дуже неефективним для великої кількості файлів.
Адам Розенфілд

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

Деякі обхідні шляхи для процесу розмноження: stackoverflow.com/questions/186099 / ...
Вінко Vrsalovic

використовуйте "-maxdepth 1", щоб уникнути рекурсії.
ejgottl

скористайтеся зворотними посиланнями, щоб отримати аналог розширення підказки оболонки оболонки: cp find -maxdepth 1 -not -name '*Music*'/ target_directory
ejgottl

2

У наступних роботах перераховані всі *.txtфайли в поточному режимі, крім тих, що починаються з числа.

Це працює bash, dash, zshі всі інші POSIX сумісних оболонок.

for FILE in /some/dir/*.txt; do    # for each *.txt file
    case "${FILE##*/}" in          #   if file basename...
        [0-9]*) continue ;;        #   starts with digit: skip
    esac
    ## otherwise, do stuff with $FILE here
done
  1. У першому рядку візерунок /some/dir/*.txtспричинить forциклічне повторення всіх файлів, на /some/dirім'я яких закінчується .txt.

  2. У другому рядку випадок справи використовується для вилучення небажаних файлів. - ${FILE##*/}Вираз знімає будь-який провідний компонент імені dir з імені файлу (тут /some/dir/), щоб малюнки могли співставити лише базове ім'я файлу. (Якщо ви відмиваєте лише назви файлів на основі суфіксів, можете скоротити це $FILEзамість цього.)

  3. У третьому рядку всі файли, що відповідають caseшаблону [0-9]*) будуть пропущені ( continueоператор переходить до наступної ітерації forциклу). - Якщо ви хочете, ви можете зробити щось більш цікаве тут, наприклад, пропустити всі файли, які не починаються з букви (a – z) [!a-z]*, або ви можете використовувати кілька шаблонів, щоб пропустити декілька типів імен файлів, наприклад, [0-9]*|*.bakщоб пропустити файли обох .bakфайлів , і файли, які не починаються з числа.


До! Була помилка (я відповідав *.txtзамість просто *). Виправлено зараз.
zrajm

0

це зробить це, виключаючи саме "Музику"

cp -a ^'Music' /target

це та інше для виключення таких речей, як Музика? * чи *? Музика

cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target

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