Resolvendo “mv: lista de argumentos muito longa”?

53

Eu tenho uma pasta com mais de um milhão de arquivos que precisam ser classificados, mas não posso fazer nada porque mv envia essa 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/
    
por Dominique 09.05.2014 / 01:31

7 respostas

78

xargs é a ferramenta para o trabalho. Isso, ou find com -exec … {} + . Essas ferramentas executam um comando várias vezes, com tantos argumentos quanto 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 para mv é o destino. Com os utilitários GNU (ou seja, no Linux ou Cygwin não integrado), a opção -t para mv é útil para passar primeiro o destino.

Se os nomes dos arquivos não tiverem espaço em branco nem \"' , você poderá simplesmente fornecer os nomes dos arquivos como entrada para xargs (o comando echo é um bash incorporado, portanto, não está sujeito à limite de comprimento da linha de comando):

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir

Você pode usar a opção -0 para xargs para usar a entrada delimitada por nulo em vez do formato padrão entre aspas.

printf '%s
find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +
' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir

Como alternativa, você pode gerar a lista de nomes de arquivos com find . Para evitar recursão 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 sh -c 'mv "$@" "$0"' targetdir/ {} +

(Isso inclui arquivos de pontos, diferente dos métodos de curingas de shell).

Se você não tem utilitários GNU, você pode usar um shell intermediário para obter os argumentos na ordem correta. Este método funciona em todos os sistemas POSIX.

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

No zsh, você pode carregar o mv builtin :

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/

ou se preferir deixar mv e outros nomes se referirem aos comandos externos:

setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/

ou com globs de estilo ksh:

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

Como alternativa, usar o GNU mv e zargs :

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir
    
por 09.05.2014 / 01:48
9

O limite de passagem de argumentos do sistema operacional não se aplica a expansões que ocorrem no interpretador de shell. Portanto, além de usar xargs ou find , podemos simplesmente usar um loop de shell para dividir o processamento em comandos mv individuais:

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done

Isso usa apenas recursos e utilitários POSIX Shell Command Language. Este one-liner é mais claro com recuo, semicolons desnecessários removidos:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done
    
por 17.09.2014 / 23:22
4

Para uma solução mais agressiva do que as oferecidas anteriormente, abra sua fonte do kernel e edite o include/linux/binfmts.h

Aumente o tamanho de MAX_ARG_PAGES para algo maior que 32. Isso aumenta a quantidade de memória que o kernel permitirá para os argumentos do programa, permitindo que você especifique seu comando mv ou rm para um milhão de arquivos ou o que for voce esta fazendo. Recompile, instale, reinicie.

CUIDADO! Se você definir isso muito grande para a memória do seu sistema e, em seguida, executar um comando com muitos argumentos BAD THINGS ACONTECERÁ! Seja extremamente cauteloso ao fazer isso com sistemas multiusuários, é mais fácil para usuários mal-intencionados usarem toda a sua memória!

Se você não sabe como recompilar e reinstalar seu kernel manualmente, provavelmente é melhor você fingir que essa resposta não existe por enquanto.

    
por 19.06.2015 / 19:26
4

Uma solução mais simples usando "$origin"/!(*.jpg|*.png|*.bmp) em vez de um bloco catch:

for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done

Obrigado a @Score_Under

Para um script com várias linhas, você pode fazer o seguinte (observe o ; antes de o done ser descartado):

for file in "$origin"/!(*.jpg|*.png|*.bmp); do        # don't copy types *.jpg|*.png|*.bmp
    mv -- "$file" "$destination" 
done 

Para fazer uma solução mais generalizada que mova todos os arquivos, você pode fazer o one-liner:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done

Que se parece com isso se você faz recuo:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 

Isso pega todos os arquivos na origem e os move um por um para o destino. As citações em torno de $file são necessárias no caso de haver espaços ou outros caracteres especiais nos nomes de arquivos.

Aqui está um exemplo desse método que funcionou perfeitamente

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
    
por 19.06.2015 / 23:07
3

Se trabalhar com o kernel do Linux é suficiente, você pode simplesmente fazer

ulimit -s 100000

que funcionará porque o kernel do Linux incluiu um patch em torno de 10 anos atrás que alterou o limite de argumento para ser baseado no tamanho da pilha: link

Atualização: Se você se sentir corajoso, pode dizer

ulimit -s unlimited

e você ficará bem com qualquer expansão de shell, desde que você tenha RAM suficiente.

    
por 01.11.2017 / 10:24
1

Você pode contornar essa restrição enquanto ainda estiver usando mv se não se importar em executá-la algumas vezes.

Você pode mover partes de cada vez. Digamos, por exemplo, que você tenha uma longa lista de nomes de arquivos alfanuméricos.

mv ./subdir/a* ./

Isso funciona. Então, mate outro grande pedaço. Depois de alguns movimentos, você pode voltar a usar mv ./subdir/* ./

    
por 12.11.2015 / 03:46
0

Às vezes é mais fácil escrever um pequeno roteiro, por exemplo em Python:

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)
    
por 11.10.2018 / 00:22