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

StackOverflow https://stackoverflow.com/questions/216995

Вопрос

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

cp [exclude-matches] *Music* /target_directory

Что должно быть вместо [исключить совпадения] для достижения этой цели?

Это было полезно?

Решение

В 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

Другие советы

Тот Самый extglob опция shell дает вам более мощное сопоставление шаблонов в командной строке.

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

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

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

Полный доступный внутреннийзакончился шарикоператорами bing являются (выдержка из man bash):

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

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

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

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

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

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

Не в bash (о котором я знаю), но:

cp `ls | grep -v Music` /target_directory

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

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

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

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

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

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

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

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

Мое личное предпочтение - использовать команду 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

Одно решение для этого можно найти с помощью 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 могут быть полезны для управления этим.

Уловка, которую я еще не видел здесь, которая не использует extglob , find или grep , заключается в обработке двух файлов перечисляет как наборы и " diff " их, используя 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)

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

В следующих работах перечислены все файлы *. 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 используется для отсеивания нежелательных файлов. & # 8211; Выражение $ {FILE ## * /} удаляет любой начальный компонент имени dir из имени файла (здесь / some / dir / ), так что шаблоны могут сопоставляться только с Базовое имя файла. (Если вы только отсеиваете имена файлов на основе суффиксов, вы можете сократить это значение до $ FILE .)

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

  • это сделало бы это, исключая именно «музыку»

    cp -a ^'Music' /target
    

    это и то, что исключает такие вещи, как музыка? * или *? музыка

    cp -a ^\*?'complete' /target
    cp -a ^'complete'?\* /target
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top