Почему !(%) в xargs работает неправильно?

Почему !(%) в xargs работает неправильно?

Я пытаюсь использовать следующий код, чтобы исключить последний файл и скопировать остальные в папку назначения:

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для работы со списками, разделенными нулями, а не разделителями новой строки. (Подстановочные знаки оболочки обычно справляются с этим нормально.)

Связанный контент