Preservar a estrutura de diretórios ao mover arquivos usando o find


22

Eu criei o seguinte script que move arquivos antigos, conforme definido do diretório de origem para o diretório de destino. Está funcionando perfeitamente.

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Files which were $days Days old moved from $soure to $destination"

Esse script move arquivos muito bem. Também move arquivos do subdiretório de origem, mas não cria subdiretórios no diretório de destino. Eu quero implementar esse recurso adicional nele.

com exemplo

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

Quando executo esse script, ele também move os arquivos hexadecimais no diretório maxi, mas preciso que o mesmo hexadecimal seja criado no diretório maxi e mova seus arquivos no mesmo hexadecimal para lá.

Respostas:


13

Em vez de executar mv /home/ketan/hex/foo /home/maxi, você precisará variar o diretório de destino com base no caminho produzido por find. Isso é mais fácil se você mudar primeiro para o diretório de origem e executar find .. Agora você pode simplesmente acrescentar o diretório de destino a cada item produzido por find. Você precisará executar um shell no find … -execcomando para executar a concatenação e criar o diretório de destino, se necessário.

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

Observe que, para evitar problemas de citação se $destinationcontiver caracteres especiais, você não pode substituí-lo apenas dentro do shell script. Você pode exportá-lo para o ambiente para que ele atinja o shell interno ou transmiti-lo como argumento (foi o que eu fiz). Você pode economizar um pouco de tempo de execução agrupando shchamadas:

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

Como alternativa, no zsh, você pode usar a zmvfunção e os qualificadores. e m glob para corresponder apenas aos arquivos regulares no período correto. Você precisará passar uma mvfunção alternativa que primeiro crie o diretório de destino, se necessário.

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'

for x do, você tem uma falta ;lá :). Além disso, não tenho ideia do que você queria alcançar, $0mas estou convencido de que seria sh:).
Michał Górny

@ MichałGórny for x; dotecnicamente não é compatível com POSIX (verifique a gramática ), mas os shells modernos permitem ambos for x doe for x; do; algumas velhas conchas Bourne não grunhiram for x; do. Nas conchas modernas, com sh -c '…' arg0 arg1 arg2 arg3, arg0torna-se $0, arg1torna-se $1, etc. Se você quer $0ser sh, precisa escrever sh -c '…' sh arg1 arg2 arg3. Novamente, algumas conchas Bourne se comportaram de maneira diferente, mas o POSIX especifica isso.
Gilles 'SO- stop be evil'

pushdparece uma escolha melhor que cd, por ser menos invasiva no ambiente atual.
precisa saber é o seguinte

16

Eu sei que findfoi especificado, mas isso parece um trabalho para rsync.

Eu costumo usar o seguinte:

rsync -axuv --delete-after --progress Source/ Target/

Aqui está um bom exemplo se você deseja mover apenas arquivos de um tipo de arquivo específico ( exemplo ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/

muito mais limpo do que as outras soluções :)
Guillaume

2
Achei isso --remove-source-filesútil, o que efetivamente faz com que os arquivos sejam movidos em vez de copiados. Este é um ótimo uso do rsync.
Tom

3

você poderia fazer isso usando duas instâncias de find (1)

Sempre tem cpio (1)

(cd "$soure" && find  | cpio -pdVmu "$destination")

Verifique os argumentos para cpio. Os que eu dei


1
Isso quebrará todos os nomes de arquivos com espaço em branco.
Chris Baixo

1
Isso copia os arquivos em vez de movê-los.
Gilles 'SO- stop be evil'

Pode não ser uma resposta perfeita, mas me ajudou a mover arquivos preservando caminhos de maneira mais ou menos direta (tenho espaço suficiente para copiar os arquivos e excluí-los). Upvoted
AhHatem

3

Não é tão eficiente, mas o código é mais fácil de ler e entender, na minha opinião, se você apenas copiar os arquivos e depois excluir.

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

Aviso: falha descoberta pelo @MV para operações automatizadas:

Usar duas operações separadas é arriscado. Se alguns arquivos tiverem mais de 7 dias enquanto a operação de cópia estiver concluída, eles não serão copiados, mas serão excluídos pela operação de exclusão. Para algo que está sendo feito manualmente, uma vez que isso pode não ser um problema, mas para scripts automatizados isso pode levar à perda de dados


2
Usar duas operações separadas é arriscado. Se alguns arquivos tiverem mais de 7 dias enquanto a operação de cópia estiver concluída, eles não serão copiados, mas serão excluídos pela operação de exclusão. Para algo que está sendo feito manualmente uma vez, isso pode não ser um problema, mas para scripts automatizados isso pode levar à perda de dados.
MV.

2
A solução fácil para essa falha é executar o find uma vez, salvar a lista como um arquivo de texto e usar xargs duas vezes para fazer a cópia e depois excluir.
David M. Perlman

1

Você pode fazer isso anexando o caminho absoluto do arquivo retornado findao seu caminho de destino:

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'

Chris agora seus movimentos casa inteira em maxi
Ketan Patel

@ K.KPatel Não, não. Apenas mantém sua estrutura de diretórios.
Chris Baixo

Isso interrompe se $destinationcontiver caracteres especiais, pois sofre expansão no shell interno. Talvez você quis dizer destination='\'"$destination"\''? Isso ainda continua '. Além disso, isso cria arquivos como, em /home/maxi/home/ketan/hex/foovez de /home/maxi/hex/foo.
Gilles 'SO- stop be evil'

0

Melhor (mais rápido e sem consumir espaço de armazenamento fazendo cópia em vez de mover), também não é afetado pelos nomes dos arquivos se eles contiverem caracteres especiais em seus nomes:

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

Ou mais rápido, movendo vários arquivos ao mesmo tempo para várias CPUs, usando o comando "paralelo":

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

PS: Você tem um erro de digitação, "soure" deve ser "fonte". Eu mantive o nome da variável.


0

Isso é menos elegante, mas fácil se o número / tamanho dos arquivos não for muito grande

Compacte seus arquivos em um ziparquivo e descompacte no destino sem a -jopção. Por padrão, o zip criará a estrutura de diretórios relativa.


0

Tente desta maneira:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done

0

Como parece não haver uma solução realmente fácil para isso e eu preciso muito disso, criei este utilitário de código aberto para linux (requer python): https://github.com/benapetr/smv

Existem várias maneiras de usá-lo para alcançar o que você precisa, mas provavelmente o mais simples seria algo como isto:

 # -vf = verbose + force (doesn't stop on errors)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

Além disso, você pode executá-lo no modo seco para não fazer nada além de imprimir o que faria

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

Ou caso essa lista de arquivos seja muito longa para caber na linha de argumento e você não se importe de executar o python para cada arquivo, então

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.