Изменение строки замены в xargs
Когда я использую xargs
, иногда мне не нужно явно использовать заменяющую строку:
find . -name "*.txt" | xargs rm -rf
В других случаях я хочу указать заменяющую строку, чтобы сделать что-то вроде:
find . -name "*.txt" | xargs -I '{}' mv '{}' /foo/'{}'.bar
Предыдущая команда переместила бы все текстовые файлы под текущим каталогом в /foo
и добавит расширение bar
ко всем файлам.
Если вместо добавления некоторого текста в строку replace мне нужно изменить эту строку, чтобы я мог вставить текст между именем и расширением файлов, как я мог это сделать? Например, скажем, я хочу сделать то же, что и в предыдущем примере, но файлы следует переименовать/переместить из <name>.txt
в /foo/<name>.bar.txt
(вместо /foo/<name>.txt.bar
).
ОБНОВЛЕНИЕ: мне удается найти решение:
find . -name "*.txt" | xargs -I{} \
sh -c 'base=$(basename $1) ; name=${base%.*} ; ext=${base##*.} ; \
mv "$1" "foo/${name}.bar.${ext}"' -- {}
Но мне интересно, есть ли более короткое/лучшее решение.
Ответы
Ответ 1
В подобных случаях цикл while
будет более читаемым:
find . -name "*.txt" | while IFS= read -r pathname; do
base=$(basename "$pathname"); name=${base%.*}; ext=${base##*.}
mv "$pathname" "foo/${name}.bar.${ext}"
done
Обратите внимание, что вы можете найти файлы с тем же именем в разных подкаталогах. Вы в порядке с дубликатами, написанными над текстом mv
?
Ответ 2
Следующая команда создает команду перемещения с помощью xargs, заменяет второе вхождение '.' с '.bar.', затем выполняет команды с bash, работая на Mac OSX.
ls *.txt | xargs -I {} echo mv {} foo/{} | sed 's/\./.bar./2' | bash
Ответ 3
Если у вас установлен GNU Parallel http://www.gnu.org/software/parallel/, вы можете сделать это:
find . -name "*.txt" | parallel 'ext="{/}" ; mv -- {} foo/{/.}.bar.${ext##*.}'
Вы можете установить GNU Parallel просто:
wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
Смотрите видеоролики для GNU. Параллельно узнайте больше:
https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Ответ 4
Это можно сделать за один проход (проверено в GNU), избегая использования временных назначений переменных
find . -name "*.txt" | xargs -I{} sh -c 'mv "$1" "foo/$(basename ${1%.*}).new.${1##*.}"' -- {}
Ответ 5
Если вам разрешено использовать нечто, отличное от bash/sh, и это просто для фантазии "mv"... вы можете попробовать почтенный "rename.pl" script. Я использую его в Linux и cygwin на окнах все время.
http://people.sc.fsu.edu/~jburkardt/pl_src/rename/rename.html
rename.pl 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' list_of_files_or_glob
Вы также можете использовать параметр "-p" для rename.pl, чтобы он рассказывал вам, что он сделал, не выполнив этого.
Я просто попробовал следующее в моей c:/bin (cygwin/windows environment). Я использовал "-p", поэтому он выплюнул, что бы он сделал. Этот пример просто разбивает базу и расширение и добавляет строку между ними.
perl c:/bin/rename.pl -p 's/^(.*?)\.(.*)$/\1-new_stuff_here.\2/' *.bat
rename "here.bat" => "here-new_stuff_here.bat"
rename "htmldecode.bat" => "htmldecode-new_stuff_here.bat"
rename "htmlencode.bat" => "htmlencode-new_stuff_here.bat"
rename "sdiff.bat" => "sdiff-new_stuff_here.bat"
rename "widvars.bat" => "widvars-new_stuff_here.bat"
Ответ 6
Вдохновленный ответом от @justaname выше, эта команда, которая включает в себя однострочный Perl, сделает это:
find ./ -name \*.txt | perl -p -e 's/^(.*\/(.*)\.txt)$/mv $1 .\/foo\/$2.bar.txt/' | bash