Lista de argumentos muito longa ao copiar arquivos

26

Acabei de fazer uma pergunta relacionada a como posso contar os arquivos de uma extensão específica. Agora eu quero cpesses arquivos para um novo dir.

Eu estou tentando,

cp *.prj ../prjshp/

e

cp * | grep '\.prj$' ../prjshp/

mas eles estão dando o mesmo erro,

bash: / bin / cp: lista de argumentos muito longa

Como eu os copio?

Sam007
fonte

Respostas:

36

cp *.prj ../prjshp/é o comando certo, mas você encontrou um caso raro em que ocorre uma limitação de tamanho. O segundo comando que você tentou não faz sentido.

Um método é executar cpnos arquivos em pedaços. O findcomando sabe como fazer isso:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find percorre o diretório atual e os diretórios abaixo dele recursivamente.
  • -maxdepth 1 significa parar na profundidade de 1, ou seja, não recursar em subdiretórios.
  • -name '*.prj'significa agir apenas nos arquivos cujo nome corresponde ao padrão especificado. Observe as aspas ao redor do padrão: ele será interpretado pelo findcomando, não pelo shell.
  • -exec … {} +significa executar o comando especificado para todos os arquivos. Ele chama o comando várias vezes, se necessário, tomando cuidado para não exceder o limite da linha de comandos.
  • mv -t ../prjshpmove os arquivos especificados para ../prjshp. A -topção é usada aqui devido a uma limitação do findcomando: os arquivos encontrados (simbolizados por {}) são passados ​​como o último argumento do comando, você não pode adicionar o destino depois dele.

Outro método é usar rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpcopia o diretório atual para ../prjshprecursivamente.
  • --include='*.prj' --exclude='*'significa copiar arquivos correspondentes *.prje excluir todo o resto (incluindo subdiretórios, para que os .prjarquivos nos subdiretórios não sejam encontrados).
Gilles 'SO- parar de ser mau'
fonte
3
rsync, de longe a solução mais fácil aqui.
Ntk4 18/07
Para ser um pouco exigente, o segundo comando cp * | grep '\.prj$' ../prjshp/ não faz nenhum sentido, mas pode ser sintaticamente válido, se for *expandido para a lista de arquivos com o último sendo um diretório (aka cp SOURCE1 SOURCE2....DEST). O pipe não faz nenhum sentido, com certeza, mas também permanece sintaticamente válido no que diz respeito ao shell - dup()os descritores de arquivo funcionam muito bem, mas o terminal do leitor não obtém dados porque cpnão grava nenhum .
Sergiy Kolodyazhnyy
O find e o rsync produziram o mesmo erro da lista de argumentos por muito tempo. O loop for foi a solução mais simples.
Meezaan-ud-Din 15/09
De fato, o rsync é a maneira de fazer cópias em massa, embora eu esteja perplexo com o quão longe chegamos com o Linux e tenhamos uma falha / bug bobo como esse e sim, eu consideraria uma falha / bug.
MitchellK
22

Este comando copia os arquivos um por um e funcionará mesmo se houver muitos deles para *expandir em um único cpcomando:

for i in *; do cp "$i" ../prjshp/; done
ccshields
fonte
Isso funciona para mim.
1rq3fea324wre
1
Simples e eficaz. Eu tive um problema semelhante ao remover ~ 1/4 milhões de jpegs que eu extraíra de um vídeo para um projeto. Essa é a abordagem que eu usei.
Elder Geek
5

Lembre-se de três pontos principais ao enfrentar um Argument list too longerro:

  • O comprimento dos argumentos da linha de comando é limitado pela ARG_MAXvariável, que, por definição do POSIX, é "... [m] comprimento máximo do argumento para as funções exec, incluindo dados do ambiente" (ênfase adicionada) ". Ou seja, quando o shell executa um comando não comando -built-it, ele deve chamar um dos exec()para gerar o processo desse comando, e é aí que ARG_MAXentra em cena.Além disso, o nome ou o caminho do próprio comando (por exemplo /bin/echo) desempenha um papel.

  • Os comandos internos do shell são executados pelo shell, o que significa que o shell não usa a exec()família de funções e, portanto, não é afetado pela ARG_MAXvariável.

  • Certos comandos, como xargse findconhecem as ARG_MAXvariáveis ​​e executam ações repetidamente sob esse limite

Pelos pontos acima e como mostrado na excelente resposta de Kusalananda sobre questões relacionadas, isso Argument list too longtambém pode ocorrer quando o ambiente é grande. Portanto, levando em consideração que o ambiente de cada usuário pode variar e o tamanho do argumento em bytes é relevante, é difícil criar um único número de arquivos / argumentos.

Como lidar com esse erro?

O principal é não se concentrar no número de arquivos, mas se o comando que você vai usar envolve ou não uma exec()família de funções e tangencialmente - o espaço da pilha.

Use built-ins do shell

Como discutido anteriormente, os embutidos no shell são imunes ao ARG_MAXlimite, ou seja, forloops, whileloops, embutidos echoe embutidos printf- todos terão bom desempenho.

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

Em questões relacionadas à exclusão de arquivos, havia uma solução como essa:

printf '%s\0' *.jpg | xargs -0 rm --

Observe que isso usa o shell interno printf. Se estivermos chamando o externo printf, isso envolverá exec(), portanto, falhará com grande número de argumentos:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

matrizes de bash

De acordo com uma resposta de jlliagre, bashnão impõe limites às matrizes, portanto, a criação de uma matriz de nomes de arquivos e o uso de fatias por iteração de loop também podem ser feitos, como mostra a resposta de danjpreron :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

Isso, no entanto, tem limitações de ser específico do bash e não POSIX.

Aumentar o espaço da pilha

Às vezes você pode ver as pessoas sugerem aumentar o espaço de pilha com ulimit -s <NUM>; no Linux, o valor ARG_MAX é 1/4 do espaço da pilha para cada programa, o que significa que aumentar o espaço da pilha aumenta proporcionalmente o espaço para argumentos.

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

De acordo com a resposta de Franck Dernoncourt , que cita o Linux Journal, também é possível recompilar o kernel do Linux com maior valor para o máximo de páginas de memória para argumentos, no entanto, isso é mais trabalhoso do que o necessário e abre potencial para explorações, conforme mencionado no artigo do Linux Journal.

Evite concha

Outra maneira, é usar pythonou python3que vem por padrão com o Ubuntu. O exemplo python + here-doc abaixo, é algo que eu pessoalmente usei para copiar um grande diretório de arquivos em algum lugar na faixa de 40.000 itens:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

Para percursos recursivos, você pode usar os.walk .

Veja também:

Sergiy Kolodyazhnyy
fonte
2

IMHO, as ferramentas ideais para lidar com hordas de arquivos são finde xargs. Veja man find. Veja man xargs. find, com sua -print0opção, produz uma NULlista separada de nomes de arquivos (os nomes de arquivos podem conter qualquer caractere executado NULou /) que xargscompreenda, usando a -0opção. xargsentão cria o comando mais longo permitido (a maioria dos nomes de arquivos, sem meio nome de arquivo no final) e o executa. xargsrepete isso até que findnão forneça mais nomes de arquivos. Corra xargs --show-limits </dev/nullpara ver os limites.

Para resolver seu problema (e depois de verificar man cppara encontrar --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
waltinator
fonte