Я пытаюсь использовать следующий код, чтобы исключить последний файл и скопировать остальные в папку назначения:
ls -Art | tail -n 1 | xargs -I% cp !(%) ~/test_dst/folder1
Первые два канала получают имя последнего файла, и это передается в xargs, который добавляет это в команду cp, где я использую подстановочный знак extglob ! для копирования всего остального. Проблема в том, что это копирует каждый файл! Я попытался отладить это, определив, что печатает первая часть этого (set -x включен):
> ~/test_src/folder1$ ls -Art | tail -n 1
+ tail -n 1
+ ls --color=auto -Art
file4
успех! это действительно правильный файл! Затем я попытался распечатать его с подстановочным знаком:
> ~/test_src/folder1$ ls -Art | tail -n 1 | xargs -I% echo !(%)
+ tail -n 1
+ ls --color=auto -Art
+ xargs -I% echo file1 file2 file3 file4
file1 file2 file3 file4
и он включает в себя пропущенный файл?? Я попробовал жестко закодировать команду отдельно, и что вы знаете, она на самом деле работает правильно:
> ~/test_src/folder1$ echo !(file4)
+ echo file1 file2 file3
file1 file2 file3
что я делаю не так? это в принципе моя первая попытка написать скрипт оболочки, и это сводит меня с ума, любые советы будут очень признательны. Я бы предпочел отладить этот конкретный метод (это просто способ, который я придумал), но я определенно открыт для любых предложений или альтернативных методов сделать это. спасибо!
решение1
Да, это не сработает. Две функции, которые вы пытаетесь объединить, интерпретируются в неправильном порядке.
Extglobs — это функция оболочки (командного интерпретатора), и, как вы заметили в выводе «trace», они раскрываются bashдокоманда 'xargs' фактически выполняется.
Если бы extglobs (или любые другие globs) передавались как есть в xargs (или в 'echo', или в 'cp'), он бы не знал, что с ними делать; например, 'cp' подумал бы, что "!(%)" или "!(file1)" — это буквально имя файла, который нужно скопировать.
xargs, с другой стороны, этонетчасть оболочки — это отдельная программа, как и любая другая, и bash не знает значения «%», а также не знает, что вы передаете xargs другую команду.
Поэтому, когда !(%)
extglob расширяется, он расширяется толькоодин раздля всей команды – не один раз для каждого ввода xargs, а только один раз для литерала, %
который Bash видит в команде. Это, конечно, приводит квсе файлы, которые не имеют имени%
.
Один из способов сделать это — заменить xargs собственными расширениями оболочки — в отличие от xargs, переменные оболочки расширяютсядоextglobs, поэтому если у вас есть имя файла в переменной...
except_file=$(ls -Art | tail -n 1)
...вы можете использовать это в extglob:
cp !("$except_file") ~/test_dst/folder1
(Их можно объединить в cp !("$(ls...)") ~/...
.)
Также можно сделать наоборот и сохранить xargs, но удалить extglob. Вместо того, чтобы получить только последний файл с помощью tail
и затем выполнять дополнительные шаги, чтобы получить все остальное, вы можете напрямую получитьвсе, кромепоследний файл, использующий head
(отрицательные параметры для «head» и «tail» означают «кроме последней N»):
ls -Art | head -n -1 | xargs -I% cp % ~/test_dst/folder1
или, чтобы позволить xargs выполнить свою работу по вызову одного файла cp
для всех файлов одновременно, предполагая, что в вашей системе есть GNU Coreutils с cp -t <target>
опцией:
ls -Art | head -n -1 | xargs -d '\n' cp -t ~/test_dst/folder1
Примечание: Имена файлов в Linux могут содержать переносы строк. Многие из приведенных выше примеров предполагают, что у вас нет таких имен файлов, потому что на самом деле их не должно быть – но этоявляетсяЭто следует учитывать при написании скриптов для развертывания на неизвестных системах, а «защитное» программирование обычно включает такие инструменты, как find ... -print0
и xargs -0
для работы со списками, разделенными нулями, а не разделителями новой строки. (Подстановочные знаки оболочки обычно справляются с этим нормально.)