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?
Em todas as respostas abaixo, pode ser necessário adicionar um "LC_ALL = ..." na frente dos comandos usando os intervalos, para que os intervalos deles usem o local correto para você (os locais podem mudar e a ordem dos caracteres pode mudar) Ex: em alguns locais, [az] pode conter todas as letras do alfabeto, exceto "Z", pois as letras são ordenadas: aAbBcC .... zZ. Existem outras variações (letras acentuadas e pedidos ainda mais exóticos Uma escolha usual é:LC_ALL=C
Olivier Dulac
Respostas:
23
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).
Como funciona
a=(*)
Isso cria uma matriz acom 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.
Para copiar e renomear ao mesmo tempo
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:
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 cdo 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.
Certificando-se de que o pedido seja consistente
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 aestiver definida. Por exemplo:
LC_ALL=C a=(*)
Você pode descobrir em qual localidade está atualmente executando o localecomando
Se você estiver usando zshvocê 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, Onclassifica 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 cpcomando para tratar os arquivos cujos nomes começam -corretamente, portanto:
cp --*(.On[1,4])~
Adicione o Dqualificador se você também quiser considerar arquivos ocultos (arquivos de ponto):
@ StéphaneChazelas sim, vamos esclarecer para futuros leitores que classificar em ordem alfabética na direção normal ( 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.
jimmij
7
Aqui está uma solução usando comandos bash extremamente simples.
Isso é muito semelhante à bashsoluçã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 cpargumentos 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 cpprimeiro argumento que pode ser interpretado como uma opção e causar falha na operação ou gerar resultados não intencionais.
Mesmo trabalhando com o diretório atual como o diretório de origem, isso é feito facilmente como ... 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.
Portanto, em um set 1 2 3 4 5caso, isso mudará a 1distância, mas em umset 1 2 3 caso, nada mudará.
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:
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 :
Obrigado! Funciona. Existe alguma maneira de anexar rapidamente um prefixo a_a TODOS os quatro arquivos? Eu tentei echo "a_$(ls | tail -n 4)", mas ele apenas precede o primeiro arquivo.
Sibbs Gambling
@SibbsGambling Minha abordagem não é adequada para isso, mas a resposta de John1024 pode ser facilmente adaptada a isso.
Hauke Laging
5
Em geral, a lssaí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".
precisa saber é o seguinte
2
@HaukeLaging Mesmo que atualmente não seja um problema (porque eu não tenho nomes de fogo "complicados" no meu diretório), talvez eu tenha daqui a dois anos e, de repente, as coisas caem em meus pés ...
glglgl
1
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:
Como você garante que findos 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
Respostas:
Isso pode ser feito facilmente com matrizes bash / ksh93 / zsh:
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
).Como funciona
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 elementosbash
).cp -- "${a[@]: -4}" ~/
Isso copia os quatro últimos nomes de arquivos para o diretório inicial.
Para copiar e renomear ao mesmo tempo
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:Copie de um diretório diferente e também renomeie
Se usarmos em
a=(./some_dir/*)
vez dea=(*)
, temos o problema do diretório sendo anexado ao nome do arquivo. Uma solução é:Outra solução é usar um subshell e
cd
o diretório no subshell:Quando o subshell é concluído, o shell retorna ao diretório original.
Certificando-se de que o pedido seja consistente
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:Você pode descobrir em qual localidade está atualmente executando o
locale
comandofonte
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 seriaAqui,
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--
aocp
comando para tratar os arquivos cujos nomes começam-
corretamente, portanto:Adicione o
D
qualificador se você também quiser considerar arquivos ocultos (arquivos de ponto):fonte
*([-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.
Explicação:
Localiza todos os arquivos no diretório atual.
Classifica em ordem alfabética.
Mostrar apenas as últimas 4 linhas.
Loops sobre cada linha executando o comando copy.
fonte
Contanto que você considere o tipo de shell agradável, basta:
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 nocp
primeiro argumento que pode ser interpretado como uma opção e causar falha na operação ou gerar resultados não intencionais.set -- ./*
ouarray=(./*)
.É 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á a1
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
:... que aplicaria uma
sed
substituição de estilo a todos os nomes de arquivos à medida que os copia.fonte
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 dels
), e não começar com.
, então você pode fazer isso :fonte
a_
a TODOS os quatro arquivos? Eu tenteiecho "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:
fonte
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?