Eu tenho uma pasta com mais de um milhão de arquivos que precisam ser classificados, mas não posso fazer nada porque mv
gera esta mensagem o tempo todo
-bash: /bin/mv: Argument list too long
Estou usando este comando para mover arquivos sem extensão:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
Responder1
xargs
é a ferramenta para o trabalho. Isso, oufind
com -exec … {} +
. Essas ferramentas executam um comando várias vezes, com tantos argumentos quantos podem ser passados de uma só vez.
Ambos os métodos são mais fáceis de executar quando a lista de argumentos variáveis está no final, o que não é o caso aqui: o argumento final mv
é o destino. Com utilitários GNU (ou seja, em Linux não embarcado ou Cygwin), a -t
opção to mv
é útil, para passar o destino primeiro.
Se os nomes dos arquivos não tiverem espaços em branco nem \"'
começarem com -
¹, você poderá simplesmente fornecer os nomes dos arquivos como entrada para xargs
(o echo
comando é um bash integrado, portanto não está sujeito ao limite de comprimento da linha de comando; se você vir !: event not found
, precisará ativar a sintaxe globbing com shopt -s extglob
):
echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir --
Você pode usar a -0
opção para xargs
usar entrada delimitada por nulo em vez do formato entre aspas padrão.
printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir --
Alternativamente, você pode gerar a lista de nomes de arquivos com find
. Para evitar a recorrência em subdiretórios, use -type d -prune
. Como nenhuma ação é especificada para os arquivos de imagem listados, apenas os outros arquivos são movidos.
find . -name . -o -type d -prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec mv -t targetdir/ {} +
(Isso inclui arquivos de ponto, diferentemente dos métodos curinga do shell.)
Se você não possui utilitários GNU, você pode usar um shell intermediário para colocar os argumentos na ordem correta. Este método funciona em todos os sistemas POSIX.
find . -name . -o -type d -prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec sh -c 'mv "$@" "$0"' targetdir/ {} +
No zsh, você pode carregar omv
construídas em:
setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/
ou se preferir deixar mv
e outros nomes continuarem se referindo aos comandos externos:
setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/
ou com globs no estilo ksh:
setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/
Alternativamente, usando GNU mv
ezargs
:
autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/ --
¹ com algumas xargs
implementações, os nomes dos arquivos também devem ser textos válidos no código do idioma atual. Alguns também considerariam um arquivo nomeado _
como indicando o fim da entrada (pode ser evitado com -E ''
)
Responder2
Se trabalhar com o kernel Linux for suficiente, você pode simplesmente fazer
ulimit -S -s unlimited
Isso funcionará porque o kernel do Linux incluiu um patch há cerca de 10 anos que alterou o limite de argumentos para ser baseado no tamanho da pilha:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=b6a2fea39318e43fee84fa7b0b90d68bed92d2ba
Se você não quiser espaço de pilha ilimitado, você pode dizer, por exemplo
ulimit -S -s 100000
para limitar a pilha a 100 MB. Observe que você precisa definir o espaço da pilha para o uso normal da pilha (geralmente 8 MB) mais o tamanho da linha de comando que deseja usar.
Você pode consultar o limite real da seguinte maneira:
getconf ARG_MAX
que produzirá o comprimento máximo da linha de comando em bytes. Por exemplo, os padrões do Ubuntu definem isso para 2097152
cerca de 2 MB. Se eu executar com pilha ilimitada, obtenho 4611686018427387903
exatamente 2 ^ 62 ou cerca de 46.000 TB. Se sua linha de comando excederque, espero que você consiga solucionar o problema sozinho.
Observe que se você usar sudo
como em sudo mv *.dat somewhere/.
execução ulimit
não poderá resolver esse problema porque sudo
redefine o tamanho da pilha antes de executar o mv
real. Para solucionar isso, você deve iniciar o shell root com e sudo -s
, em seguida, executar ulimit -S -s unlimited
e, finalmente, executar o comando sem sudo
esse shell root.
Responder3
Às vezes é mais fácil escrever apenas um pequeno script, por exemplo, em Python:
import glob, shutil
for i in glob.glob('*.jpg'):
shutil.move(i, 'new_dir/' + i)
Responder4
O limite de passagem de argumentos do sistema operacional não se aplica a expansões que acontecem dentro do interpretador shell. Portanto, além de usar xargs
or find
, podemos simplesmente usar um loop de shell para dividir o processamento em mv
comandos individuais:
for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done
Isso usa apenas recursos e utilitários da linguagem de comando POSIX Shell. Esta linha única fica mais clara com recuo, com pontos e vírgulas desnecessários removidos:
for x in *; do
case "$x" in
*.jpg|*.png|*.bmp)
;; # nothing
*) # catch-all case
mv -- "$x" target
;;
esac
done