Как использовать обратные или отрицательные подстановочные знаки при сопоставлении шаблонов в оболочке unix/linux?

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

cp [exclude-matches] *Music* /target_directory

Что следует делать вместо [exclude-matches] для выполнения этого?

Ответ 1

В 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

Ответ 2

Параметр оболочки extglob обеспечивает более мощное сопоставление с образцом в командной строке.

Вы включаете его с помощью shopt -s extglob, а выключаете его с помощью shopt -u extglob.

В вашем примере вы бы сначала сделали:

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

Полные доступные дополнительные завершенные глобальные операторы bing (выдержка из man bash):

Если опция оболочки extglob включена с помощью встроенной функции shopt, несколько расширенных Операторы сопоставления с образцом распознаются.Список шаблонов - это список из одного или нескольких шаблонов, разделенных знаком |. Составные шаблоны могут быть сформированы с использованием одного или нескольких из следующих подэлементов:

  • ? (Шаблон)
    Соответствует нулю или одному вхождению данных паттернов
  • * (Шаблон)
    Соответствует нулю или более вхождений заданных шаблонов
  • + (Шаблон)
    Соответствует одному или нескольким вхождениям указанных шаблонов
  • @(Шаблон)
    Соответствует одному из заданных шаблонов
  • ! (Шаблон)
    Совпадает с чем угодно, кроме одного из заданных шаблонов

Так, например, если вы хотите перечислить все файлы в текущем каталоге, которые не являются файлами .c или .h, вы должны сделать:

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

Конечно, нормальное сглаживание оболочки работает, поэтому последний пример также можно записать так:

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

Ответ 3

Не в bash (что я знаю), но:

cp `ls | grep -v Music` /target_directory

Я знаю, что это не совсем то, что вы искали, но оно решит ваш пример.

Ответ 4

Если вы хотите избежать затрат на использование команды 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/

Ответ 5

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

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

Ответ 6

В bash, альтернативой shopt -s extglob является переменная GLOBIGNORE. Это не совсем лучше, но мне легче запомнить.

Пример, который может быть тем, что хотел оригинальный плакат:

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

Когда закончите, unset GLOBIGNORE чтобы иметь возможность rm *techno* в исходном каталоге.

Ответ 7

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

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

Ответ 8

Трюк, который я еще не видел здесь, который не использует extglob, find или grep, должен обрабатывать два списка файлов как наборы и "различать" их с помощью comm:

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

comm предпочтительнее, чем diff, потому что у него нет лишней жесткости.

Это возвращает все элементы набора 1, ls, которые также не входят в набор 2, ls *Music*. Это требует, чтобы оба набора были в порядке сортировки для правильной работы. Нет проблем для ls и расширения glob, но если вы используете что-то вроде find, обязательно вызывайте sort.

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

Потенциально полезный.

Ответ 9

Одно решение для этого можно найти с помощью find.

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

Найти есть довольно много вариантов, вы можете получить довольно конкретную информацию о том, что вы включаете и исключаете.

Изменить: Адам в комментариях отметил, что это рекурсивно. найти варианты mindepth и maxdepth могут быть полезны в управлении этим.

Ответ 10

В следующих работах перечислены все *.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
  • В первой строке шаблон /some/dir/*.txt вызовет цикл цикла for для всех файлов в /some/dir, чье имя заканчивается на .txt.

  • Во второй строке аргумент case используется для отсечения нежелательных файлов. - Выражение ${FILE##*/} удаляет любой ведущий компонент имени dir из имени файла (здесь /some/dir/), так что паттерны могут соответствовать только базовому имени файла. (Если вы только отбираете имена файлов на основе суффиксов, вы можете сократить это до $FILE.)

  • В третьей строке все файлы, соответствующие строке case pattern [0-9]*), будут пропущены (оператор continue переходит к следующей итерации цикла for). - Если вы хотите, чтобы вы могли сделать что-то более интересное здесь, например. например, пропустить все файлы, которые не начинаются с буквы (a-z) с помощью [!a-z]*, или вы можете использовать несколько шаблонов для пропуска нескольких типов имен файлов, например. [0-9]*|*.bak пропустить файлы как файлы .bak, так и файлы, которые не начинаются с числа.

Ответ 11

это сделало бы это, исключая точно "Музыка"

cp -a ^'Music' /target

для этого и для исключения таких вещей, как Music? * или *? Music

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