Resolvendo “mv: lista de argumentos muito longa”?

Resolvendo “mv: lista de argumentos muito longa”?

Eu tenho uma pasta com mais de um milhão de arquivos que precisam ser classificados, mas não posso fazer nada porque mvgera 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, oufindcom -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 -topçã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 echocomando é 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 -0opção para xargsusar 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 omvconstruídas em:

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/

ou se preferir deixar mve 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 mvezargs:

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/ --

¹ com algumas xargsimplementaçõ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 2097152cerca de 2 MB. Se eu executar com pilha ilimitada, obtenho 4611686018427387903exatamente 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 sudocomo em sudo mv *.dat somewhere/.execução ulimitnão poderá resolver esse problema porque sudoredefine o tamanho da pilha antes de executar o mvreal. Para solucionar isso, você deve iniciar o shell root com e sudo -s, em seguida, executar ulimit -S -s unlimitede, finalmente, executar o comando sem sudoesse 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 xargsor find, podemos simplesmente usar um loop de shell para dividir o processamento em mvcomandos 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

informação relacionada