Mover uma lista de arquivos, combinando-os com um índice de destino

3

Digamos que eu tenha dois arquivos de texto src.txt e dest.txt , onde src.txt contém uma lista de nomes de arquivos (alguns dos quais incluem espaços) em /src/dir/ e dest.txt contém, em ordem aleatória, uma lista de os caminhos de arquivo completos (novamente com espaços) onde eles pertencem. Por exemplo:

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

Como posso executar essa operação de movimentação em lote a partir do shell? Eu tenho trabalhado com um loop while read sobre o arquivo de origem, e tenho certeza que preciso usar o comando mv , mas não tenho certeza se grep ou sed são necessários aqui. Eu continuo correndo em cannot stat... e erros de resolução de caracteres espaciais.

    
por Jake Brown 05.11.2015 / 23:24

4 respostas

3

com zsh :

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

Isto lê cada arquivo em uma matriz e, em seguida, para cada elemento na matriz "dest" , se o nome da base ( :t for um modificador zsh que remove todos os componentes principais do caminho) também está no array "src" e move o arquivo. Para executar uma execução a seco, substitua mv por printf '"%s" -> "%s"\n' .

Agora, você também pode executar (ainda em zsh ):

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

que funciona bem, desde que nenhum dos nomes de arquivo em src.txt corresponda a nenhum dos nomes de diretório (ou parte desse nome) na lista de caminhos em dest.txt (por exemplo, um nome de arquivo data1 in src.txt e um caminho como /path/data1_dir/some_file in dest.txt daria um falso positivo). Para evitar isso, você poderia passar os nomes dos arquivos para grep como padrões (ou seja, usando regex como /filename$ ) em vez de F ixed para corresponder apenas ao último componente dos caminhos em dest.txt . Embora isso exija a exclusão de todos os caracteres especiais (se houver) nos nomes de arquivos em src.txt , por exemplo, desta vez com 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
    
por 06.11.2015 / 01:02
2

Se uma nova linha é um delimitador aceitável, o seguinte deve ser bastante robusto em um shell POSIX:

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

Existem dois problemas possíveis com essa solução que posso imaginar:

  • O tamanho do arquivo de entrada é simplesmente muito grande para dividir de uma só vez assim.

    • No meu sistema, isso realmente não leva em consideração, até que a entrada se aproxime de dezenas de milhares de linhas.
  • Um nome de arquivo em $destfile pode existir no diretório atual e ainda assim não ser movido de qualquer maneira.

    • Como esta solução não compara os dois arquivos de entrada completamente e apenas verifica cada último componente de nome de caminho em $destfile para existência no diretório atual, se algum nome de arquivo puder ser encontrado acidentalmente, não deve ser considerado.

Se apenas o primeiro problema precisar ser tratado:

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

Se o seu sh for dash , você poderá descartar . /dev/fd/0 no final e usar:

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

... porque dash trata estranhamente as opções de linha de comando e de chamada stdin em conjunto e sem reclamação. Isso não seria muito portátil, mas . /dev/fd/0 - embora bastante portátil - não é estritamente compatível com os padrões.

Se a segunda questão é uma preocupação:

export  LC_ALL=C 
sed  -ne'\|/$|!s|.*/\(.*\)|/&|p' <"$destfile" |
sort -t/ -k1,1 - ./"$srcfile"  |  cut  -d/ -f2- |
sed  -e "\|/|!N;\|\n.*/|!d"    \
     -e "s|'|'"'\&&|g'        \
     -e "s|\n|' '|;s|.*|mv './&'|" | sh

... que deve lidar com isso muito bem, desde que todos os nomes de arquivos em ./"$srcfile" sejam apropriadamente e identicamente explicados no final de algum caminho em "$destfile" . sort sempre exibirá a menor das duas comparações idênticas ao topo e, portanto, quando apenas o primeiro campo é importante, e o nome do arquivo é acrescentado à cabeça de cada nome de caminho de "$destfile" , em seguida, uma operação mesclada sort de ambos os arquivos irão produzir seqüências como:

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

... e você só precisa se preocupar com pares de linhas começando com um que não corresponde a / .

    
por 10.11.2015 / 05:32
1
while read i; do echo cp \""$i"\" \"$(grep "/$i$" dst.txt)\"; done < src.txt

Isto irá imprimir o que teria sido feito. Apenas se livre do echo para realmente copiar os arquivos.

    
por 06.11.2015 / 00:07
0

Um script de uma linha gera um script que gera um script.

Neste exemplo, usamos uma primeira chamada de sed on src.txt para gerar um segundo script sed que será executado em dest.txt para gerar um script de shell para copiar os arquivos.

Aqui está o one-liner:

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

e a saída:

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

Anote o comentário #| sh no final do comando. Dessa forma, você pode tentar o comando e ver o que ele fará, e se estiver bom, remova o comentário do pipe para sh e realmente copie os arquivos.

O comando inner sed constrói um script sed fora de src.txt. A primeira linha do script gerado se parece com isso:

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

É assim que funciona:

Entrada:

    $ 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

Primeira invocação de sed . Isso mostra o script gerado que será interpretado pela segunda chamada de sed :

$ sed 's,\(..*\),/\/$/ { s/^/cp "" "/; 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; }

Use a subestação do comando shell para usar a saída do primeiro comando sed como um script na linha de comando passado para a segunda chamada de sed :

$ sed -n "$(sed 's,\(..*\),/\/$/ { s/^/cp "" "/; 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";

Agora, canalize a saída de sed para o shell, com a opção xtrace ( sh -x ). Eu não tenho nenhum dos arquivos, daí os erros:

$ sed -n "$(sed 's,\(..*\),/\/$/ { s/^/cp "" "/; 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
    
por 06.11.2015 / 02:36

Tags