“Lista de argumentos muito longa”: como faço para lidar com isso sem alterar meu comando?

18

Quando executo um comando como ls */*/*/*/*.jpg, recebo o erro

-bash: /bin/ls: Argument list too long

Eu sei por que isso acontece: é porque há um limite de kernel na quantidade de espaço para argumentos em um comando. O conselho padrão é alterar o comando que eu uso, para evitar exigir muito espaço para argumentos (por exemplo, use finde xargs).

E se eu não quiser mudar o comando? E se eu quiser continuar usando o mesmo comando? Como faço para que as coisas "funcionem" sem obter esse erro? Que soluções estão disponíveis?

DW
fonte
Leitura útil: Bash FAQ 95 . Sem alterar seu comando, não há muito o que fazer além de recompilar para aumentar o tamanho máximo da lista de argumentos ou alterar a estrutura de diretórios para que haja menos arquivos.
Jw013 15/08
11
@ jw013 baseado na versão do kernel do linux, pode ser possível aumentar a lista de argumentos - consulte unix.stackexchange.com/a/45161/8979 para obter detalhes sobre a mudança nos sistemas recentes.
Ulrich Dangel
@UlrichDangel, Sim, é possível! Veja minha resposta; minha resposta mostra como fazê-lo (no Linux, com um kernel recente o suficiente).
DW

Respostas:

26

No Linux, a quantidade máxima de espaço para argumentos de comando é 1/4 da quantidade de espaço disponível na pilha. Portanto, uma solução é aumentar a quantidade de espaço disponível para a pilha.

Versão curta: execute algo como

ulimit -s 65536

Versão mais longa: a quantidade padrão de espaço disponível para a pilha é algo como 8192 KB. Você pode ver a quantidade de espaço disponível, da seguinte maneira:

$ ulimit -s
8192

Escolha um número maior e defina a quantidade de espaço disponível para a pilha. Por exemplo, se você quiser tentar permitir até 65536 KB para a pilha, execute o seguinte:

$ ulimit -s 65536

Talvez você precise brincar com o tamanho que isso precisa ser, usando tentativa e erro. Em muitos casos, esta é uma solução rápida e suja que irá eliminar a necessidade de modificar o comando e trabalho fora a sintaxe find, xargsetc. (embora eu percebo que há outros benefícios para fazê-lo).

Eu acredito que isso seja específico do Linux. Eu suspeito que provavelmente não ajudará em nenhum outro sistema operacional Unix (não testado).

DW
fonte
11
Você pode verificar assim que funcionou: $ getconf ARG_MAX 2097152 $ ulimit -s 65535 $ getconf ARG_MAX 16776960
Alex
2

Este artigo do Linux Journal fornece 4 soluções. Somente a quarta solução não envolve a alteração do comando:

O método 4 envolve aumentar manualmente o número de páginas alocadas no kernel para argumentos da linha de comando. Se você observar o arquivo include / linux / binfmts.h, encontrará o seguinte na parte superior:

/*
 * MAX_ARG_PAGES defines the number of pages allocated for   arguments
 * and envelope for the new program. 32 should suffice, this gives
 * a maximum env+arg of 128kB w/4KB pages!
 */
#define MAX_ARG_PAGES 32

Para aumentar a quantidade de memória dedicada aos argumentos da linha de comando, você simplesmente precisa fornecer o valor MAX_ARG_PAGES com um número maior. Depois que essa edição é salva, basta recompilar, instalar e reiniciar no novo kernel, como faria normalmente.

No meu próprio sistema de teste, consegui resolver todos os meus problemas aumentando esse valor para 64. Após testes extensivos, não tive um único problema desde a troca. Isso é totalmente esperado, já que, mesmo com MAX_ARG_PAGESdefinido como 64, a maior linha de comando possível que eu poderia produzir ocuparia apenas 256 KB de memória do sistema - não muito pelos padrões de hardware do sistema de hoje.

As vantagens do método # 4 são claras. Agora você pode simplesmente executar o comando como faria normalmente, e ele é concluído com êxito. As desvantagens são igualmente claras. Se você aumentar a quantidade de memória disponível para a linha de comando além da quantidade de memória disponível no sistema, poderá criar um ataque do DOS no seu próprio sistema e causar uma falha. Em sistemas multiusuário, em particular, mesmo um pequeno aumento pode ter um impacto significativo, pois cada usuário recebe a memória adicional. Portanto, sempre teste extensivamente em seu próprio ambiente, pois essa é a maneira mais segura de determinar se o Método 4 é uma opção viável para você.

Concordo que a limitação é seriamente irritante.

Franck Dernoncourt
fonte
1

Em vez de ls */*/*/*/*.jpg, tente:

echo */*/*/*/*.jpg | xargs ls

xargs(1) sabe qual é o número máximo de argumentos no sistema e interromperá sua entrada padrão para chamar a linha de comando especificada várias vezes, sem mais argumentos que esse limite, qualquer que seja (você também pode configurá-lo abaixo de máximo do sistema operacional usando a -nopção).

Por exemplo, suponha que o limite seja de 3 argumentos e você tenha cinco arquivos. Nesse caso, xargsserá executado lsduas vezes:

  1. ls 1.jpg 2.jpg 3.jpg
  2. ls 4.jpg 5.jpg

Geralmente isso é perfeitamente adequado, mas nem sempre - por exemplo, você não pode confiar em ls(1) classificar todas as entradas para você corretamente, porque cada lsconvite -in separado separará apenas o subconjunto de entradas atribuído a ele xargs.

Embora você possa aumentar o limite conforme sugerido por outras pessoas, ainda haverá um limite - e um dia sua coleção de JPG superará isso novamente. Você deve preparar seu script para lidar com um número infinito ...

Mikhail T.
fonte
Obrigado pela ideia! Esta não é uma solução alternativa incorreta. Duas advertências: 1. Isso quebra nos diretórios e nomes de arquivos que possuem espaços em seus nomes, portanto não é um substituto perfeito. 2. Isso vai ter o mesmo problema com Argument list too long, mas em echovez de ls, em shells onde echonão há um comando interno do shell? (Talvez isso não é um problema na maioria dos shells, talvez isso é irrelevante.)
DW
11
Sim, caracteres especiais nos nomes de arquivos são um problema. Sua melhor aposta é usar findcom o -print0predicado - e canalizar sua saída xargscom a -0opção echoé um shell interno e não sofre com a limitação da linha de comando de exec(3).
21417 Mikhail T.