Eu tenho muitos arquivos que são ordenados pelo nome do arquivo em um diretório. Desejo copiar os arquivos N (digamos, N = 4) finais para o meu diretório pessoal. Como devo fazer isso?
cp ./<the final 4 files> ~/
Eu tenho muitos arquivos que são ordenados pelo nome do arquivo em um diretório. Desejo copiar os arquivos N (digamos, N = 4) finais para o meu diretório pessoal. Como devo fazer isso?
cp ./<the final 4 files> ~/
Respostas:
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, guias, novas linhas ou outros caracteres difíceis (supondo que haja pelo menos 4 arquivos não ocultos no diretório atual bash
).
a=(*)
Isso cria uma matriz a
com todos os nomes de arquivo. Os nomes de arquivo retornados pelo bash são classificados em ordem alfabética. (Presumo que seja isso que você quer dizer com "ordenado pelo nome do arquivo". )
${a[@]: -4}
Isso retorna os últimos quatro elementos da matriz a
(desde que a matriz contenha pelo menos 4 elementos bash
).
cp -- "${a[@]: -4}" ~/
Isso copia os quatro últimos nomes de arquivos para o diretório inicial.
Isso copiará os últimos quatro arquivos apenas para o diretório inicial e, ao mesmo tempo, acrescentará a sequência a_
ao nome do arquivo copiado:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
Se usarmos em a=(./some_dir/*)
vez de a=(*)
, temos o problema do diretório sendo 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
o diretório no subshell:
(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done)
Quando o subshell é concluído, o shell retorna ao diretório original.
A pergunta pede arquivos "ordenados pelo nome do arquivo". Olivier Dulac ressalta que a ordem nos comentários varia de um local para outro. Se for importante ter resultados fixos independentemente das configurações da máquina, é melhor especificar explicitamente o código do idioma quando a matriz a
estiver definida. Por exemplo:
LC_ALL=C a=(*)
Você pode descobrir em qual localidade está atualmente executando o locale
comando
Se você estiver usando zsh
você pode colocar entre parênteses ()
uma lista dos chamados qualificadores glob que selecionar os arquivos desejados. No seu caso, isso seria
cp *(On[1,4]) ~/
Aqui, On
classifica os nomes dos arquivos em ordem alfabética na ordem inversa e [1,4]
leva apenas os 4 primeiros.
Você pode tornar isso mais robusto selecionando apenas arquivos simples (excluindo diretórios, pipes etc.) com .
e também anexando --
ao cp
comando para tratar os arquivos cujos nomes começam -
corretamente, portanto:
cp -- *(.On[1,4]) ~
Adicione o D
qualificador se você também quiser considerar arquivos ocultos (arquivos de ponto):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) é o comportamento padrão, então não há necessidade de especificar isso e, -
na frente dos números, os nomes dos arquivos são contados na parte inferior.
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 em ordem alfabética.
tail -n 4
Mostrar apenas as últimas 4 linhas.
while read -r file; do cp "$file" ~/; done
Loops sobre cada linha executando o comando copy.
Contanto que você considere o tipo de shell agradável, basta:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
Isso é muito semelhante à bash
solução de matriz específica oferecida, mas deve ser portátil para qualquer shell compatível com POSIX. Algumas notas sobre os dois métodos:
É importante que você levar seus cp
argumentos w / cp --
ou você recebe uma das formas de .
um ponto ou /
à frente de cada nome. Se você não conseguir fazer isso, corre o risco de um -
traço principal no cp
primeiro argumento que pode ser interpretado como uma opção e causar falha na operação ou gerar resultados não intencionais.
set -- ./*
ou array=(./*)
.É importante ao usar os dois métodos para 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
único acontece se houver pelo menos 4 itens na matriz arg - e apenas afastar os primeiros args que produzem um excedente de 4.
set 1 2 3 4 5
caso, isso mudará a 1
distância, mas em umset 1 2 3
caso, nada mudará.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 sed
substituição de estilo a todos os nomes de arquivos à medida que os copia.
Se houver apenas arquivos e seus nomes não contiverem espaços 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 não começar com .
, então você pode fazer isso :
cp -- $(ls | tail -n 4) ~/
a_
a TODOS os quatro arquivos? Eu tentei echo "a_$(ls | tail -n 4)"
, mas ele apenas precede o primeiro arquivo.
ls
saída de análise é considerada incorreta porque normalmente falha horrivelmente em nomes de arquivos que contêm não apenas espaços, mas também guias, novas linhas ou outros caracteres válidos, mas "difíceis".
Esta é uma solução simples do bash sem nenhum código de loop. Copie os 4 últimos arquivos do diretório atual para o destino:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
find
os arquivos sejam entregues na ordem desejada? Como você garante que os arquivos que contêm caracteres de espaço em branco (incluindo novas linhas) em seus nomes sejam manipulados corretamente?
LC_ALL=C