
Допустим, у меня есть два текстовых файла src.txt
и dest.txt
, где src.txt
содержит список имен файлов (некоторые из которых включают пробелы) в /src/dir/
и dest.txt
содержит, в случайном порядке, список полных путей к файлам (опять же с пробелами), где они находятся. Например:
src.txt:
file 1.jpg
file_2.html
file 3.jpg
dest.txt:
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
Как мне выполнить эту операцию пакетного перемещения из оболочки? Я работал с while read
циклом по исходному файлу и почти уверен, что мне нужно использовать команду mv
, но я не уверен, нужны ли grep
они sed
здесь. Я постоянно сталкиваюсь с cannot stat...
ошибками разрешения пробелов.
решение1
С zsh
:
src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f
Это считывает каждый файл в массиве, а затем для каждого элемента в"дест"массив, если базовое имя ( :t
модификатор zsh
, который удаляет все начальные компоненты пути) также находится в"источник"массив затем перемещает файл. Для выполнения пробного запуска замените mv
на printf '"%s" -> "%s"\n'
.
Теперь вы также можете запустить (все еще в zsh
):
for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f
что отлично работает, пока ни одно из имен файлов в src.txt
не совпадает ни с одним из имен каталогов (или частью этого имени) в списке путей в dest.txt
(например, имя файла data1
в src.txt
и путь, как /path/data1_dir/some_file
в, dest.txt
дадут ложное срабатывание). Чтобы избежать этого, вы можете передать имена файлов в grep
как шаблоны (т. е. используя регулярное выражение, как /filename$
) вместо F
фиксированных строк, чтобы сопоставить только последний компонент путей в dest.txt
. Хотя для этого требуется экранировать все специальные символы (если они есть) в именах файлов в src.txt
, например, в этот раз с помощью bash
( 4
):
readarray -t files < <(sed 's|[[\.*^$/]|\\&|g;s|.*|/&$|' src.txt | grep -f- dest.txt)
for f in "${files[@]}"; do mv /src/dir/"${f##*/}" "$f"; done
решение2
Если символ новой строки является приемлемым разделителем, то следующий код должен быть достаточно надежным в оболочке POSIX:
IFS='
';set -f
for f in $(cat <"$destfile")
do [ -e "./${f##*/}" ] ||
[ -h "./${f##*/}" ] &&
mv "./${f##*/}" "$f"
done
Я могу себе представить две возможные проблемы с этим решением:
Размер входного файла слишком велик, чтобы его можно было разделить за один раз.
- На моей системе это даже не стоит серьезно рассматривать, пока объем входных данных не приблизится к нескольким десяткам тысяч строк.
Имя файла
$destfile
может существовать в текущем каталоге и все же должнонетбыть перемещены в любом случае.- Поскольку это решение полностью исключает сравнение двух входных файлов и проверяет только
$destfile
существование каждого последнего компонента имени пути в текущем каталоге, то если какие-либо имена файлов могут непреднамеренно совпасть, это не следует рассматривать.
- Поскольку это решение полностью исключает сравнение двух входных файлов и проверяет только
Если необходимо решить только первую проблему:
sed -ne"s|'|'"'\\&&|g' <"$destfile" \
-e "s|.*/\([^/].*\)|_mv './\1' '&'|p" |
sh -c '_mv(){ [ -e "$1" ]||[ -h "$1" ]&& mv "$@";};. /dev/fd/0'
Если у вас sh
is, dash
вы можете опустить the . /dev/fd/0
в конце и использовать:
sed ... | sh -cs '_mv(){ ...;}'
...потому что dash
странно обрабатывает как опции вызова командной строки, так и stdin в согласованном порядке и без жалоб. Это было бы не очень переносимо, но . /dev/fd/0
- хотя и довольно переносимо - также не совсем соответствует стандартам.
Если вас беспокоит вторая проблема:
export LC_ALL=C
sed -ne'\|/$|!s|.*/\(.*\)|\1/&|p' <"$destfile" |
sort -t/ -k1,1 - ./"$srcfile" | cut -d/ -f2- |
sed -e "\|/|!N;\|\n.*/|!d" \
-e "s|'|'"'\\&&|g' \
-e "s|\n|' '|;s|.*|mv './&'|" | sh
...который должен очень хорошо с этим справляться, пока все имена файлов ./"$srcfile"
правильно и идентично учтены в конце некоторого пути в "$destfile"
. sort
всегда будет перемещать наверх более короткое из двух в остальном идентичных сравнений, и поэтому, когда имеет значение только первое поле, а имя файла добавляется к началу каждого имени пути, то "$destfile"
объединенная sort
операция обоих файлов выведет последовательности вроде:
$srcfile: no /
$destfile: match
$destfile: unique
$destfile: unique
...
$srcfile: no /
$destfile: match
$destfile: unique
...и поэтому вам нужно беспокоиться только о парах строк, начинающихся с несовпадающей /
.
решение3
while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt
Это распечатает то, что было бы сделано. Просто избавьтесь от , echo
чтобы фактически скопировать файлы.
решение4
Однострочный скрипт генерирует скрипт, который генерирует скрипт.
В этом примере мы используем первый вызов sed
on src.txt
для генерации второго sed
скрипта, который будет запущен dest.txt
для генерации скрипта оболочки для копирования файлов.
Вот однострочный ответ:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt #| sh -x
и вывод:
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
Обратите внимание на комментарий #| sh
в конце команды. Таким образом, вы можете попробовать команду и посмотреть, что она сделает, и если она хороша, раскомментируйте канал to sh
и действительно скопируйте файлы.
Внутренняя команда sed создает скрипт sed из src.txt. Первая строка сгенерированного скрипта выглядит так:
/\/file 1.jpg$/ { s/^/cp file 1.jpg /; p; }
Вот как это работает:
Вход:
$ cat src.txt
file 1.jpg
file_2.html
file 3.jpg
$ cat dest.txt
/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html
Первый sed
вызов. Здесь показан сгенерированный скрипт, который будет интерпретирован вторым вызовом sed
:
$ sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt
/\/file 1.jpg$/ { s/^/cp "file 1.jpg" "/; s/$/";/; p; }
/\/file_2.html$/ { s/^/cp "file_2.html" "/; s/$/";/; p; }
/\/file 3.jpg$/ { s/^/cp "file 3.jpg" "/; s/$/";/; p; }
Используйте подстановку команд оболочки, чтобы использовать вывод первой sed
команды в качестве скрипта в командной строке, передаваемой второму вызову sed
:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt
cp "file 3.jpg" "/dest/dir 1/file 3.jpg";
cp "file 1.jpg" "/dest/dir 2/file 1.jpg";
cp "file_2.html" "/dest/file_2.html";
Теперь передайте вывод sed в оболочку с опцией xtrace ( sh -x
). У меня нет ни одного файла, отсюда и ошибки:
$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt | sh -x
+ cp file 3.jpg /dest/dir 1/file 3.jpg
cp: cannot stat ‘file 3.jpg’: No such file or directory
+ cp file 1.jpg /dest/dir 2/file 1.jpg
cp: cannot stat ‘file 1.jpg’: No such file or directory
+ cp file_2.html /dest/file_2.html
cp: cannot stat ‘file_2.html’: No such file or directory