Скажем, я хочу скопировать содержимое каталога, исключая файлы и папки, имена которых содержат слово "Музыка".
cp [exclude-matches] *Music* /target_directory
Что следует делать вместо [exclude-matches] для выполнения этого?
В 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
Полные доступные операторы ext end glob bing (выдержка из man bash
):
Если опция оболочки extglob включена с использованием встроенного магазина, несколько расширенных распознаются операторы сопоставления шаблонов. В следующем описании, pat tern-list - список одного или нескольких шаблонов, разделенных символом |. Композитные узоры может быть сформирован с использованием одного или нескольких из следующих подматриц:
- ? (Шаблон)
Соответствует нулю или одному вхождению данных шаблонов- * (шаблон)
Соответствует нулю или более вхождению данных шаблонов- + (шаблон)
Соответствует одному или нескольким вхождениям данных шаблонов- @(шаблон)
Соответствует одному из заданных шаблонов- ! (Шаблон)
Совпадает со всем, кроме одного из заданных шаблонов
Итак, например, если вы хотите перечислить все файлы в текущем каталоге, которые не являются .c
или .h
файлами, вы должны:
$ ls -d !(*@(.c|.h))
Конечно, нормальный раскол оболочки работает, поэтому последний пример также можно записать как:
$ ls -d !(*.[ch])
.c
или .h
является каталогом.
Не в 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/
Вы также можете использовать довольно простой цикл for
:
for f in `find . -not -name "*Music*"`
do
cp $f /target/dir
done
-maxdepth 1
для нерекурсивного?
Мои личные предпочтения - использовать команду 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
bash
вы можете использовать, while IFS='' read -r filename
, но тогда новые строки остаются проблемой. В общем случае лучше не использовать ls
для перечисления файлов; инструменты типа find
гораздо лучше подходят.
for file in *; do case ${file} in (*Music*) ;; (*) cp "${file}" /target_directory ; echo ;; esac; 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 могут быть полезны в управлении этим.
В bash альтернативой shopt -s extglob
является GLOBIGNORE
variable. Это не лучше, но мне легче запомнить.
Примером может быть то, что хотел исходный плакат:
GLOBIGNORE="*techno*"; cp *Music* /only_good_music/
Когда закончите, unset GLOBIGNORE
, чтобы иметь возможность rm *techno*
в исходном каталоге.
Трюк, который я еще не видел здесь, который не использует 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)
Потенциально полезный.
comm -23 <(ls) exclude_these.list
В следующих работах перечислены все *.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
, так и файлы, которые не начинаются с числа.
*.txt
вместо *
). Исправлено сейчас.
это сделало бы это, исключая точно "Музыка"
cp -a ^'Music' /target
для этого и для исключения таких вещей, как Music? * или *? Music
cp -a ^\*?'complete' /target
cp -a ^'complete'?\* /target
cp
в MacOS есть опция -a
но она делает что-то совершенно другое. Какая платформа поддерживает это?
ls /dir/*/!(base*)