Перемещение списка файлов путем сопоставления их с целевым индексом

Перемещение списка файлов путем сопоставления их с целевым индексом

Допустим, у меня есть два текстовых файла 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'

Если у вас shis, 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

Однострочный скрипт генерирует скрипт, который генерирует скрипт.

В этом примере мы используем первый вызов sedon 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

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