A análise da saída de nãols
é confiável .
Em vez disso, use find
para localizar os arquivos e sort
ordená-los por carimbo de data / hora. Por exemplo:
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
# do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
O que tudo isso está fazendo?
Primeiro, os find
comandos 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 carimbo de data / hora
- Um espaço
- O caminho relativo para o arquivo
- Um caractere NULL
O registro de data e hora é importante. O %T@
especificador de formato para -printf
divide em T
, que indica "Hora da última modificação" do arquivo (mtime) e @
que indica "Segundos desde 1970", incluindo segundos fracionários.
O espaço é apenas um delimitador arbitrário. O caminho completo para o arquivo é para que possamos consultá-lo mais tarde, e o caractere NULL é um terminador, pois é um caractere ilegal em um nome de arquivo e, portanto, nos permite saber com certeza que chegamos ao final do caminho para o arquivo Arquivo.
Eu incluí 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 sendo excluídos sejam suprimidas.
O resultado do find
comando é uma lista de todos os diretórios no diretório atual. A lista é canalizada para a sort
qual é instruído a:
-z
Trate NULL como o caractere terminador de linha em vez de nova linha.
-n
Classificar numericamente
Como os segundos desde 1970 sempre aumentam, queremos o arquivo cujo carimbo de data e hora foi o menor número. O primeiro resultado de sort
será a linha que contém o menor carimbo de data e hora numerado. Tudo o que resta é extrair o nome do arquivo.
Os resultados do find
, sort
encanamento é passado através de substituição processo para while
onde se lê como se fosse um arquivo em stdin. while
por sua vez, invoca read
para processar a entrada.
No contexto read
, definimos a IFS
variável como zero, o que significa que o espaço em branco não será interpretado inadequadamente como um delimitador. read
é contada -r
, que desativa a expansão fuga, e -d $'\0'
, o que torna o delimitador NULL fim-de-linha, combinando a saída do nosso find
, sort
pipeline.
O primeiro pedaço de dados, que representa o caminho do arquivo mais antigo, precedido por seu carimbo de data e hora e um espaço, é lido na variável line
. Em seguida, a substituição de parâmetro é usada com a expressão #*
, que simplesmente substitui todos os caracteres desde o início da string até o primeiro espaço, incluindo o espaço, por nada. Isso retira o registro de data e hora da modificação, deixando apenas o caminho completo para o arquivo.
Nesse ponto, o nome do arquivo está armazenado $file
e você pode fazer o que quiser com ele. Quando você terminar de fazer algo com $file
a while
instrução, o loop read
será executado e o comando será executado novamente, extraindo o próximo pedaço e o próximo nome do arquivo.
Não existe uma maneira mais simples?
Não. Maneiras mais simples são problemáticas.
Se você usar o ls -t
pipe para head
ou tail
(ou qualquer outra coisa ), interromperá os arquivos com novas linhas nos nomes dos arquivos. Se você, mv $(anything)
então, arquivos com espaço em branco no nome causarão falhas. Se você, mv "$(anything)"
então, os arquivos com novas linhas à direita no nome causarão interrupções. Se você read
não, -d $'\0'
então você quebrará arquivos com espaços 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 nos scripts, se puder evitar fazê-lo.
Solução
#!/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 $'\0' line ; do
file="${line#* }"
echo mv "$file" "$dest"
let limit-=1
[[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
Ligue como:
move-oldest /mnt/backup/ /var/log/foo/ 20
Para mover os 20 arquivos mais antigos de /var/log/foo/
para /mnt/backup/
.
Observe que estou incluindo arquivos e diretórios. Para arquivos, adicione apenas -type f
a find
chamada.
obrigado
Obrigado ao enzotib e ao Павел Танков pelas melhorias nesta resposta.