Como obter os últimos N arquivos em um diretório?

13

Eu tenho muitos arquivos que são ordenados pelo nome do arquivo em um diretório. Desejo copiar os arquivos finais N (digamos, N = 4) para meu diretório pessoal. Como devo fazer isso?

cp ./<the final 4 files> ~/

    
por Sibbs Gambling 26.01.2015 / 06:17

5 respostas

22

Isso pode ser feito facilmente com matrizes bash / ksh93 / zsh:

a=(*)
cp -- "${a[@]: -4}" ~/

Isso funciona para todos os nomes de arquivos não ocultos, mesmo que contenham espaços, tabulações, novas linhas ou outros caracteres difíceis (supondo que haja pelo menos quatro arquivos não ocultos no diretório atual com bash ).

Como funciona

  • a=(*)

    Isso cria uma matriz a com todos os nomes de arquivos. Os nomes dos arquivos retornados pelo bash são classificados alfabeticamente. (Eu suponho que isso é o que você quer dizer com "ordenada pelo nome do arquivo". )

  • ${a[@]: -4}

    Isso retorna os quatro últimos elementos da matriz a (desde que a matriz contenha pelo menos 4 elementos com bash ).

  • cp -- "${a[@]: -4}" ~/

    Isto copia os últimos quatro nomes de arquivos para o seu diretório pessoal.

Para copiar e renomear ao mesmo tempo

Isso copiará os últimos quatro arquivos somente para o diretório pessoal e, ao mesmo tempo, pré-anexará a string a_ ao nome do arquivo copiado:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done

Copie de um diretório diferente e renomeie

Se usarmos a=(./some_dir/*) em vez de a=(*) , teremos o problema de o diretório ser anexado ao nome do arquivo. Uma solução é:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done

Outra solução é usar um subshell e cd no diretório da subshell:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 

Quando a subshell é concluída, o shell nos retorna ao diretório original.

Certificar-se de que a encomenda é consistente

A pergunta pede por arquivos "ordenados pelo nome do arquivo". Essa ordem, Olivier Dulac aponta nos comentários, irá variar de um local para outro. Se for importante ter resultados fixos independentes das configurações da máquina, é melhor especificar a localidade explicitamente quando a matriz a for definida. Por exemplo:

LC_ALL=C a=(*)

Você pode descobrir em qual localidade você está atualmente executando o comando locale .

    
por 26.01.2015 / 06:38
8

Se você estiver usando zsh , você pode colocar entre parênteses () uma lista dos chamados glob qualifiers que selecionam os arquivos desejados. No seu caso, isso seria

cp *(On[1,4]) ~/

Aqui On classifica os nomes dos arquivos em ordem alfabética inversa e [1,4] recebe apenas os primeiros 4 deles.

Você pode tornar isso mais robusto selecionando apenas arquivos simples (exceto diretórios, canais etc.) com . e também anexando o comando -- to cp para tratar arquivos cujos nomes começam com - corretamente, então:

cp -- *(.On[1,4]) ~

Adicione o qualificador D se você também quiser considerar arquivos ocultos (arquivos de pontos):

cp -- *(D.On[1,4]) ~
    
por 26.01.2015 / 08:44
8

Aqui está uma solução usando comandos bash extremamente simples.

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

Explicação:

find . -maxdepth 1 -type f

Localiza todos os arquivos no diretório atual.

sort

Classifica por ordem alfabética.

tail -n 4

Mostrar apenas as últimas 4 linhas.

while read -r file; do cp "$file" ~/; done

Faz um loop sobre cada linha que executa o comando de cópia.

    
por 26.01.2015 / 17:54
6

Desde que você ache o tipo de concha aceitável, basta fazer:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir

Isso é muito semelhante à solução de array bash -specific oferecida, mas deve ser portável para qualquer shell compatível com POSIX. Algumas notas sobre os dois métodos:

  1. É importante que você lidere seus cp argumentos w / cp -- ou obtenha um dos . um ponto ou / à frente de cada nome. Se você não fizer isso, você corre o risco de ter um primeiro argumento - em cp , que pode ser interpretado como uma opção e fazer com que a operação falhe ou, caso contrário, renderize resultados indesejados.

    • Mesmo trabalhando com o diretório atual como o diretório de origem, isso é feito facilmente como ... set -- ./* ou array=(./*) .
  2. É importante, ao usar os dois métodos, garantir que você tenha pelo menos tantos itens em sua matriz arg quanto tentar remover - eu faço isso aqui com uma expansão matemática. O shift só acontece se houver pelo menos 4 itens no array arg - e, então, apenas desloca os primeiros argumentos que fazem um excedente de 4 ..

    • Em um caso set 1 2 3 4 5 , ele deslocará o 1 , mas, em um caso set 1 2 3 , ele não mudará nada.
    • Por exemplo: a=(1 2 3); echo "${a[@]: -4}" imprime uma linha em branco.

Se você estiver copiando de um diretório para outro, poderá usar pax :

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir

... que aplicaria uma substituição em sed aos nomes de todos os arquivos, conforme os copia.

    
por 26.01.2015 / 14:04
4

Se houver apenas arquivos e seus nomes não contiverem espaço em branco ou nova linha (e você não tiver modificado $IFS ) ou caracteres glob (ou alguns caracteres não imprimíveis com algumas implementações de ls ), e don ' Comece com . , então você pode fazer isso:

cp -- $(ls | tail -n 4) ~/
    
por 26.01.2015 / 06:38