Mover una lista de archivos comparándolos con un índice de destino

Mover una lista de archivos comparándolos con un índice de destino

Digamos que tengo dos archivos de texto src.txty dest.txt, donde src.txtcontiene una lista de nombres de archivos (algunos de los cuales incluyen espacios) /src/dir/y dest.txtcontiene, en orden aleatorio, una lista de las rutas completas de los archivos (nuevamente con espacios) a las que pertenecen. Por ejemplo:

src.txt:

file 1.jpg
file_2.html
file 3.jpg

destino.txt:

/dest/dir 1/file 3.jpg
/dest/file4.txt
/dest/file 5.txt
/dest/dir 2/file 1.jpg
/dest/file_2.html

¿Cómo puedo realizar esta operación de movimiento por lotes desde el shell? He estado trabajando con un while readbucle sobre el archivo fuente y estoy bastante seguro de que necesito usar el mvcomando, pero no estoy seguro de si grepson sednecesarios aquí. Sigo encontrando cannot stat...errores de resolución de caracteres de espacio.

Respuesta1

Con zsh:

src=(${(f)"$(<src.txt)"})
for f (${(f)"$(<dest.txt)"})
(($src[(Ie)$f:t])) && mv /src/dir/$f:t $f

Esto lee cada archivo en una matriz y luego, para cada elemento en la"destino"matriz, si el nombre base ( :tes un zshmodificador que elimina todos los componentes principales del nombre de ruta) también está en el"src"matriz luego mueve el archivo. Para realizar un funcionamiento en seco, reemplácelo mvcon printf '"%s" -> "%s"\n'.


Ahora, también puedes ejecutar (aún en zsh):

for f (${(f)"$(grep -Ff src.txt dest.txt)"})
mv /src/dir/$f:t $f

lo cual funciona bien siempre que ninguno de los nombres de archivos src.txtcoincida con ninguno de los nombres de directorio (o parte de ese nombre) en la lista de rutas (por ejemplo , dest.txtun nombre de archivo y una ruta como en daría un falso positivo). Para evitar eso, puede pasar los nombres de los archivos como patrones (es decir, usando expresiones regulares como ) en lugar de cadenas fijas para que coincidan solo con el último componente de las rutas en . Aunque eso requiere escapar de todos los caracteres especiales (si los hay) en los nombres de archivos , por ejemplo, esta vez con ( ):data1src.txt/path/data1_dir/some_filedest.txtgrep/filename$Fdest.txtsrc.txtbash4

readarray -t files < <(sed 's|[[\.*^$/]|\\&|g;s|.*|/&$|' src.txt | grep -f- dest.txt)
for f in "${files[@]}"; do mv /src/dir/"${f##*/}" "$f"; done

Respuesta2

Si una nueva línea es un delimitador aceptable, entonces lo siguiente debería ser bastante sólido en un shell POSIX:

IFS='
';set -f
for   f in $(cat <"$destfile")
do    [ -e "./${f##*/}" ] ||
      [ -h "./${f##*/}" ] &&
      mv   "./${f##*/}"  "$f"
done

Hay dos posibles problemas con esa solución que puedo imaginar:

  • El tamaño del archivo de entrada es simplemente demasiado grande para dividirlo de una sola vez.

    • En mi sistema, esto ni siquiera merece una consideración seria hasta que la entrada se acerca a muchas decenas de miles de líneas.
  • Es posible que exista un nombre de archivo $destfileen el directorio actual y aún así deberíanoser movido de todos modos.

    • Debido a que esta solución renuncia por completo a comparar los dos archivos de entrada y solo verifica $destfilela existencia de cada último componente de nombre de ruta en el directorio actual, si algún nombre de archivo puede coincidir involuntariamente, no debe considerarse.

Si sólo es necesario solucionar el primer problema:

sed -ne"s|'|'"'\\&&|g' <"$destfile"    \
    -e "s|.*/\([^/].*\)|_mv './\1' '&'|p" | 
sh  -c '_mv(){ [ -e "$1" ]||[ -h "$1" ]&& mv "$@";};. /dev/fd/0'

Si shes así, dashpuedes dejar el . /dev/fd/0al final y usar:

sed ... | sh -cs '_mv(){ ...;}'

...porque dashextrañamente maneja las opciones de invocación de línea de comandos y estándar en conjunto y sin quejarse. Eso no sería muy portátil, pero . /dev/fd/0, aunque bastante portátil, tampoco cumple estrictamente con los estándares.

Si el segundo problema es motivo de preocupación:

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

... eso debería manejarlo muy bien siempre que todos los nombres de archivos ./"$srcfile"estén contabilizados de manera adecuada e idéntica al final de alguna ruta en "$destfile". sortsiempre hará flotar la más corta de dos comparaciones idénticas en la parte superior, por lo que cuando solo importa el primer campo y el nombre del archivo se antepone al encabezado de cada nombre de ruta, "$destfile"una operación fusionada sortde ambos archivos generará secuencias como:

$srcfile:  no /
$destfile: match
$destfile: unique
$destfile: unique
...
$srcfile:  no /
$destfile: match
$destfile: unique

...y por lo tanto sólo necesita preocuparse por los pares de líneas que comienzan con una que no coincide /.

Respuesta3

while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt

Esto imprimirá lo que se habría hecho. Simplemente deshazte de él echopara copiar los archivos.

Respuesta4

Un guión de una sola línea genera un guión que genera un guión.

En este ejemplo, utilizamos una primera invocación de sedon src.txtpara generar un segundo sedscript que se ejecutará dest.txtpara generar un script de shell para copiar los archivos.

Aquí está la frase:

$ sed -n "$(sed 's,\(..*\),/\\/\1$/ { s/^/cp "\1" "/; s/$/";/; p; },' src.txt)" dest.txt #| sh -x

y la salida:

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";

Tenga en cuenta el comentario #| shal final del comando. De esta manera, puede probar el comando y ver qué hace y, si es bueno, descomentar la canalización shy copiar realmente los archivos.

El comando sed interno crea un script sed a partir de src.txt. La primera línea del script generado se ve así:

/\/file 1.jpg$/ { s/^/cp file 1.jpg /; p; }

Así es como funciona:

Aporte:

    $ 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

Primera sedinvocación. Esto muestra el script generado que será interpretado por la segunda invocación de 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; }

Utilice la sustitución de comandos de shell para utilizar la salida del primer sedcomando como un script en la línea de comando pasada a la segunda invocación de 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";

Ahora, canalice la salida de sed al shell, con la opción xtrace ( sh -x). No tengo ninguno de los archivos, de ahí los errores:

$ 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

información relacionada