Script de shell para mover arquivos mais antigos?

14

Como escrevo um script para mover apenas os 20 arquivos mais antigos de uma pasta para outra? Existe uma maneira de pegar os arquivos mais antigos em uma pasta?

    
por user11598 16.10.2011 / 01:25

7 respostas

13

Analisando a saída de ls não é confiável .

Em vez disso, use find para localizar os arquivos e sort para encomendá-los por timestamp. Por exemplo:

while IFS= read -r -d $'
#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'
move-oldest /mnt/backup/ /var/log/foo/ 20
' line ; do file="${line#* }" echo mv "$file" "$dest" let limit-=1 [[ $limit -le 0 ]] && break done < <(find "$source" -maxdepth 1 -printf '%T@ %p
while IFS= read -r -d $'
#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'
move-oldest /mnt/backup/ /var/log/foo/ 20
' line ; do file="${line#* }" echo mv "$file" "$dest" let limit-=1 [[ $limit -le 0 ]] && break done < <(find "$source" -maxdepth 1 -printf '%T@ %p%pre%' \ 2>/dev/null | sort -z -n)
' line ; do file="${line#* }" # do something with $file here done < <(find . -maxdepth 1 -printf '%T@ %p%pre%' \ 2>/dev/null | sort -z -n)
' \ 2>/dev/null | sort -z -n)
' line ; do file="${line#* }" # do something with $file here done < <(find . -maxdepth 1 -printf '%T@ %p%pre%' \ 2>/dev/null | sort -z -n)

O que tudo isso está fazendo?

Primeiro, os comandos find localizam todos os arquivos e diretórios no diretório atual ( . ), mas não nos subdiretórios do diretório atual ( -maxdepth 1 ), depois imprimem:

  • Um registro de data e hora
  • Um espaço
  • O caminho relativo para o arquivo
  • Um caractere NULL

O registro de data e hora é importante. O %T@ do especificador de -printf divide em T , o que indica "Hora da última modificação" do arquivo (mtime) e @ , que indica "Segundos desde 1970", incluindo os segundos fracionários.

O espaço é meramente um delimitador arbitrário. O caminho completo para o arquivo é para que possamos nos referir a ele mais tarde, e o caractere NULL é um terminador porque é um caractere ilegal em um nome de arquivo e, portanto, nos informa com certeza que chegamos ao final do caminho para o arquivo. arquivo.

Eu incluí o 2>/dev/null para que os arquivos que o usuário não tem permissão para acessar sejam excluídos, mas as mensagens de erro sobre eles serem excluídos sejam suprimidas.

O resultado do comando find é uma lista de todos os diretórios no diretório atual. A lista é canalizada para sort , que é instruído para:

  • -z Trate NULL como o caractere terminador de linha em vez de nova linha.
  • -n Ordenar numericamente

Como os segundos desde 1970 sempre sobem, queremos o arquivo cujo registro de data e hora seja o menor número. O primeiro resultado de sort será a linha que contém o menor registro de data e hora numerado. Tudo o que resta é extrair o nome do arquivo.

Os resultados do find , sort pipeline são transmitidos por meio de substituição de processos para while onde é lido como se fosse um arquivo em stdin. while invoca read para processar a entrada.

No contexto de read , definimos a variável IFS como nada, o que significa que o espaço em branco não será interpretado de forma inadequada como um delimitador. read é informado -r , que desativa a expansão de escape e -d $'find' , o que torna o delimitador de fim de linha NULL correspondente à saída de nosso sort , line pipeline.

O primeiro bloco de dados, que representa o caminho de arquivo mais antigo, precedido por seu registro de data e hora e espaço, é lido na variável #* . Em seguida, a substituição de parâmetros é usada com a expressão $file , que simplesmente substitui todos os caracteres do início da string até o primeiro espaço, incluindo o espaço, sem nada. Isso remove o registro de data e hora da modificação, deixando apenas o caminho completo para o arquivo.

Neste ponto, o nome do arquivo é armazenado em $file e você pode fazer o que quiser com ele. Quando terminar de fazer algo com while , a instrução read fará um loop e o comando ls -t será executado novamente, extraindo o próximo trecho e o próximo nome de arquivo.

Não existe uma maneira mais simples?

Não. Formas mais simples são bugs.

Se você usar head e pipe para tail ou mv $(anything) (ou qualquer coisa ), você quebrará arquivos com novas linhas nos nomes dos arquivos. Se você mv "$(anything)" , os arquivos com espaço em branco no nome causarão a quebra. Se você read , os arquivos com novas linhas no nome causarão a quebra. Se você -d $'/var/log/foo/' sem /mnt/backup/ , então você quebrará arquivos com espaço em branco em seus nomes.

Talvez, em casos específicos, você tenha certeza de que uma maneira mais simples é suficiente, mas nunca deve escrever suposições como essa em scripts, se puder evitar fazê-lo.

Solução

%pre%

Ligue para:

%pre%

Para mover os 20 arquivos mais antigos de -type f para find .

Note que estou incluindo os diretórios e . Para arquivos, apenas adicione %code% à invocação %code% .

Obrigado

Graças a enzotib e Павел Танков para melhorias nesta resposta.

    
por 16.01.2012 / 08:20
4

É mais fácil em zsh, onde você pode usar o qualificador de glob Om para classificar as correspondências por data (mais antigas primeiro) e o [1,20] qualificador para reter apenas as primeiras 20 correspondências:

mv -- *(Om[1,20]) target/

Adicione o qualificador D se quiser incluir arquivos de ponto também. Adicione . se quiser corresponder apenas aos arquivos regulares e não aos diretórios.

Se você não tem zsh, aqui está um one-liner Perl (você pode fazer isso em menos de 80 caracteres, mas a um custo adicional em clareza):

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

Com apenas ferramentas POSIX ou até mesmo bash ou ksh, classificar arquivos por data é uma tarefa difícil. Você pode fazê-lo facilmente com ls , mas analisar a saída de ls é problemático, portanto isso só funciona se os nomes dos arquivos contiverem apenas caracteres imprimíveis diferentes de novas linhas.

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done
    
por 16.10.2011 / 02:23
4

Combine ls -t output com tail ou head .

Exemplo simples, que funciona apenas se todos os nomes de arquivo contiverem apenas caracteres imprimíveis diferentes de espaço em branco e \[*? :

 mv $(ls -1tr | head -20) other_folder
    
por 16.10.2011 / 01:31
2

Você pode usar o GNU find para isso:

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

Onde encontrar imprime a hora da modificação (em segundos a partir de 1970) e o nome de cada arquivo do diretório atual, a saída é classificada de acordo com o primeiro campo, os 20 mais antigos são filtrados e movidos para dest_dir . Remova o echo se você testou a linha de comando.

    
por 16.10.2011 / 11:14
2

Ninguém postou (ainda) um exemplo básico que atende a caracteres de nova linha incorporados (nada embutido) no nome do arquivo, então aqui está um. Move os 3 arquivos regulares mais antigos (mdate)

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p
# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest
" |sort -znk1 | { while IFS= read -d $'
  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*
' -r file; do printf "%s
move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p
# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest
" |sort -znk1 | { while IFS= read -d $'
  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*
' -r file; do printf "%s%pre%" "${file#*$'\t'}" ((--move==0)) && break done } |xargs -0 mv -t dest
" "${file#*$'\t'}" ((--move==0)) && break done } |xargs -0 mv -t dest

Este é o snippet dados de teste

%pre%

Aqui está o check-results snippet

%pre%     
por 22.10.2011 / 13:31
0

É mais fácil fazer com o GNU find . Eu uso todos os dias no meu DVR do Linux para excluir gravações do meu sistema de vigilância por vídeo com mais de um dia.

Aqui está a sintaxe:

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

Lembre-se de que find define um dia como 24 horas a partir do momento da execução. Portanto, os arquivos modificados pela última vez às 11 da noite não serão excluídos à uma da manhã.

Você pode até combinar find com cron , portanto, as exclusões podem ser agendadas automaticamente executando o seguinte comando como root:

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

Você sempre pode obter mais informações sobre find consultando sua página de manual:

man find
    
por 12.01.2012 / 18:48
0

como as outras respostas não se encaixam no meu propósito e as perguntas, este shell é testado no CentOS 7:

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
    
por 04.05.2017 / 19:42