Tenho 11 arquivos com espaços no nome em uma pasta e quero copiar os 10 mais recentes. Eu costumava ls -t | head -n 10
obter apenas os 10 arquivos mais recentes. Quando quero usar a expressão em uma instrução cp, recebo um erro informando que os arquivos não foram encontrados devido ao espaço no nome.
Ex.: cp: cannot stat ‘10’: No such file or directory
para um arquivo chamado10 abc.pdf
A declaração cp:cp $(ls -t | head -n 10) ~/...
Como faço para que isso funcione?
Responder1
Se você estiver usando o Bash e quiser um método 100% seguro (o que, eu acho, você quer, agora que aprendeu da maneira mais difícil que deve lidar com nomes de arquivos com seriedade), aqui está:
shopt -s nullglob
while IFS= read -r file; do
file=${file#* }
eval "file=$file"
cp "$file" Destination
done < <(
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done | sort -rn | head -n10
)
Vamos dar uma olhada em suas diversas partes:
for f in *; do
printf '%s %q\n' "$(date -r "$f" +'%s')" "$f"
done
Isso imprimirá os termos padrão do formulário
Timestamp Filename
onde timestamp é a data de modificação (obtida com -r
a opção de date
) em segundos, já que Epoch e Filename é uma versão entre aspas do nome do arquivoque pode ser reutilizado como entrada do shell(veja help printf
e o %q
especificador de formato). Essas linhas são então classificadas (numericamente, em ordem inversa) com sort
e apenas as 10 primeiras são mantidas.
Isso é então alimentado no while
loop. Os carimbos de data e hora são removidos com a file=${file# *}
atribuição (isso elimina tudo, inclusive o primeiro espaço), então a linha aparentemente perigosa eval "file=$file"
elimina os caracteres de escape introduzidos por printf %q
e, finalmente, podemos copiar o arquivo com segurança.
Provavelmente não é a melhor abordagem ou implementação, mas é 100% seguro e garantido em relação a quaisquer nomes de arquivos possíveis e dá conta do recado. Porém, isso tratará arquivos, diretórios regulares, etc. Se você quiser restringir a arquivos normais, adicione [[ -f $f ]] || continue
logo após a for f in *; do
linha. Além disso, não considerará arquivos ocultos. Se você quiser arquivos ocultos (mas não .
nem ..
, é claro), adicione shopt -s dotglob
.
Outra solução 100% Bash é usar o Bash diretamente para classificar os arquivos. Aqui está uma abordagem usando um quicksort:
quicksort() {
# quicksorts the filenames passed as positional parameters
# wrt modification time, newest first
# return array is quicksort_ret
if (($#==0)); then
quicksort_ret=()
return
fi
local pivot=$1 oldest=() newest=() f
shift
for f; do
if [[ $f -nt $pivot ]]; then
newest+=( "$f" )
else
oldest+=( "$f" )
fi
done
quicksort "${oldest[@]}"
oldest=( "${quicksort_ret[@]}" )
quicksort "${newest[@]}"
quicksort_ret+=( "$pivot" "${oldest[@]}" )
}
Em seguida, classifique-os, guarde os 10 primeiros e copie-os para o seu destino:
$ shopt -s nullglob
$ quicksort *
$ cp -- "${quicksort_ret[@]:0:10}" Destination
Da mesma forma que o método anterior, este tratará arquivos regulares, diretórios, etc. da mesma forma e ignorará os arquivos ocultos.
Para outra abordagem: se você ls
tiver os argumentos i
e q
, poderá usar algo como estes:
ls -tiq | head -n10 | cut -d ' ' -f1 | xargs -I X find -inum X -exec cp {} Destination \; -quit
Isso mostrará o inode do arquivo e find
poderá executar comandos em arquivos referenciados por seu inode.
A mesma coisa, isso também tratará diretórios, etc., não apenas arquivos normais. Eu realmente não gosto deste, pois depende muito do ls
formato de saída de ...
Responder2
Para qualquer grupo de arquivos em um diretório, isso sempre deixará de fora o mais antigo:
less_oldest() {
while [ -e "$1" ] ; do
for f do [ "$f" -ot "$1" ] && break
done && { set -- "$@" "$1"
shift ; continue
} ; shift ; break
done
printf '///%s///\n' "$@" |
sed "s|'"'|&"&"&|g;'"s|///|'|g"
}
É totalmente portátil e imprime uma matriz de nomes de arquivos entre aspas seguras, independentemente dos caracteres que eles possam retornar. No seu caso, como você só precisa largar, você só precisa executá-lo uma vez, como:
{ printf 'cp -t ~"/..."'
less_oldest "${dir_of_11_files}/"*
} | sh