Erro 'Lista de argumentos muito longa' ao copiar um grande número de arquivos

12

Estou usando o seguinte comando:

\cp -uf /home/ftpuser1/public_html/ftparea/*.jpg /home/ftpuser2/public_html/ftparea/

E estou recebendo o erro:

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

Eu também tentei:

ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} /home/ftpuser2/public_html/ftparea/

Ainda tem -bash: / bin / ls: lista de argumentos muito longa

Alguma ideia?

icelizard
fonte
Estou tentando copiar todos os jpgs de um diretório para outro, mas apenas novos arquivos e arquivos que foram atualizados.
icelizard
lsnão foi projetado para fazer esse tipo de coisa. Use find.
Pausado até novo aviso.
O problema não é com ls, é com o número de argumentos que o shell está passando para ls. Você obteria o mesmo erro com o vi ou com qualquer comando não incorporado.
chris
Mas não lsfoi especialmente projetado para fazer isso: mywiki.wooledge.org/ParsingLs
Pausado até novo aviso.
É verdade, mas, neste caso, o erro não se deve a um erro de análise com ls, mas ao passar um bilhão de argumentos para um novo processo que passa a ser ls. Além de ser um uso inadequado de ls, também ocorre contra uma limitação de recurso / design do unix. Nesse caso, o paciente tem uma dor de estômago e uma perna quebrada.
chris

Respostas:

19

* .jpg se expande para uma lista maior do que o shell pode suportar. Tente isso em vez disso

find  /home/ftpuser/public_html/ftparea/ -name "*.jpg" -exec cp -uf "{}" /your/destination \;
Shawn Chin
fonte
Eu usei find / home / ftpuser1 / public_html / ftparea / -name "* jpg" -exec cp -uf "{}" / home / ftpuser2 / public_html / ftparea / e obtive o seguinte erro find: argumento ausente para `-exec '
icelizard
Você está perdendo o último argumento do cp, o atendedor lhe disse certo. Verifique sua implementação. Observe que nesta resposta o ponto em "* .jpg" está ausente, isso pode levar a comportamentos inadequados (cp um diretório chamado "myjpg", por exemplo). Nota então que pode ser paranóico, mas mais seguro especificar de perto o que você está indo para copiar usando o arquivo do tipo (impedindo diretórios, links simbólicos e assim por diante a ser afetado)
drAlberT
Após uma inspeção mais detalhada, perdi o "\;" para concluir o comando que -exec deve executar. Parvo eu!
icelizard
@AlberT: obrigado pelas cabeças re o ponto que falta. Isso foi um erro de digitação. Resposta atualizada.
Shawn Chin
Não é que o cp não consiga lidar com isso. A concha não pode.
precisa
6

Há um limite máximo para quanto tempo uma lista de argumentos pode ter para comandos do sistema - esse limite é específico da distribuição com base no valor de MAX_ARG_PAGESquando o kernel é compilado e não pode ser alterado sem recompilar o kernel.

Devido à maneira como o globbing é tratado pelo shell, isso afetará a maioria dos comandos do sistema quando você usar o mesmo argumento ("* .jpg"). Como o glob é processado pelo shell primeiro e depois enviado ao comando, o comando:

cp -uf *.jpg /targetdir/

é essencialmente o mesmo para o shell, como se você tivesse escrito:

cp -uf 1.jpg 2.jpg ... n-1.jpg n.jpg /targetdir/

Se você está lidando com muitos jpegs, isso pode se tornar incontrolável muito rapidamente. Dependendo da sua convenção de nomenclatura e do número de arquivos que você realmente precisa processar, é possível executar o comando cp em um subconjunto diferente do diretório por vez:

cp -uf /sourcedir/[a-m]*.jpg /targetdir/
cp -uf /sourcedir/[n-z]*.jpg /targetdir/

Isso poderia funcionar, mas exatamente o quão eficaz seria se baseia em quão bem você pode dividir sua lista de arquivos em blocos convenientes.

Globbable. Eu gosto dessa palavra.

Alguns comandos, como o find e o xargs , podem lidar com grandes listas de arquivos sem criar listas de argumentos de tamanho reduzido.

find /sourcedir/ -name '*.jpg' -exec cp -uf {} /targetdir/ \;

O argumento -exec executará o restante da linha de comando uma vez para cada arquivo encontrado por find , substituindo o {} por cada nome de arquivo encontrado. Como o comando cp é executado apenas em um arquivo por vez, o limite da lista de argumentos não é um problema.

Isso pode ser lento devido à necessidade de processar cada arquivo individualmente. Usar xargs poderia fornecer uma solução mais eficiente:

find /sourcedir/ -name '*.jpg' -print0 | xargs -0 cp -uf -t /destdir/

O xargs pode pegar a lista completa de arquivos fornecida pelo find e dividi-la em listas de argumentos de tamanhos gerenciáveis ​​e executar cp em cada uma dessas sublistas.

Obviamente, também há a possibilidade de recompilar seu kernel, configurando um valor maior para MAX_ARG_PAGES. Mas recompilar um kernel é mais trabalhoso do que estou disposto a explicar nesta resposta.

goldPseudo
fonte
Eu não tenho idéia do por que isso foi rejeitado. É a única resposta que parece estar explicando por que isso está acontecendo. Talvez porque você não sugeriu o uso de xargs como uma otimização?
chris
adicionado na solução xargs, mas ainda estou preocupado que os votos negativos sejam causados ​​por algo flagrantemente errado nos meus detalhes e ninguém queira me dizer o que é. :(
goldPseudo
xargsparece ser muito mais eficiente, pois o número resultante de chamadas de comando é muito menor. No meu caso, eu vejo desempenho de 6 a 12 vezes melhor argsao usar a -execsolução com um número crescente de arquivos, aumentando a eficiência.
Jan Vlcinsky
3

Isso acontece porque sua expressão curinga ( *.jpg) excede o limite de tamanho do argumento da linha de comando quando expandida (provavelmente porque você tem muitos arquivos .jpg abaixo /home/ftpuser/public_html/ftparea).

Existem várias maneiras de contornar essa limitação, como usar findor xargs. Dê uma olhada neste artigo para obter mais detalhes sobre como fazer isso.

mfriedman
fonte
+1 para o bom recurso externo sobre o assunto.
Viam0Zah 20/10/2009
3

Como GoldPseudo comentou, há um limite para quantos argumentos você pode passar para um processo que está gerando. Veja a resposta dele para uma boa descrição desse parâmetro.

Você pode evitar o problema não passando muitos argumentos no processo ou reduzindo o número de argumentos que está passando.

Um loop for no shell, find e ls, grep e um loop while fazem a mesma coisa nessa situação -

for file in /path/to/directory/*.jpg ; 
do
  rm "$file"
done

e

find /path/to/directory/ -name '*.jpg' -exec rm  {} \;

e

ls /path/to/directory/ | 
  grep "\.jpg$" | 
  while
    read file
  do
    rm "$file"
  done

todos têm um programa que lê o diretório (o próprio shell, localize e sl) e um programa diferente que, na verdade, recebe um argumento por execução e itera por toda a lista de comandos.

Agora, isso será lento porque a rm precisa ser bifurcada e executada para cada arquivo que corresponda ao padrão * .jpg.

É aqui que o xargs entra em jogo. O xargs recebe entrada padrão e para cada N (para freebsd é por padrão 5000) linhas, gera um programa com N argumentos. xargs é uma otimização dos loops acima, porque você só precisa bifurcar programas 1 / N para iterar todo o conjunto de arquivos que lêem argumentos na linha de comando.

chris
fonte
1

A glob '*' está se expandindo para muitos nomes de arquivos. Use find / home / ftpuser / public_html -name '* .jpg'.

William Pursell
fonte
Localizar e ecoar * resultam na mesma saída - a chave aqui é usar xargs, não apenas passando todos os 1 bilhão de argumentos de linha de comando para o comando que o shell está tentando bifurcar.
chris
echo * falhará se houver muitos arquivos, mas a descoberta será bem-sucedida. Além disso, usar find -exec com + é equivalente a usar xargs. (Nem todos encontram suporte +)
William Pursell
1

Usar a +opção para find -execacelerará bastante a operação.

find  /home/ftpuser/public_html/ftparea/ -name "*jpg" -exec cp -uf -t /your/destination "{}" +

A +opção precisa {}ser o último argumento, portanto, use a opção -t /your/destination(ou --target-directory=/your/destination) para cpfazê-lo funcionar.

De man find:

comando -exec {}

          This  variant  of the -exec action runs the specified command on  
          the selected files, but the command line is built  by  appending  
          each  selected file name at the end; the total number of invoca  
          tions of the command will  be  much  less  than  the  number  of  
          matched  files.   The command line is built in much the same way  
          that xargs builds its command lines.  Only one instance of  ‘{}’  
          is  allowed  within the command.  The command is executed in the  
          starting directory.

Editar : argumentos reorganizados para cp

Pausado até novo aviso.
fonte
Estou recebendo find: argumento ausente para `-exec '/ home / ftpuser1 / public_html / ftparea / -name' * jpg '-exec cp -uf" {} "/ home / ftpuser2 / public_html / ftparea / +
icelizard
Reorganizei os argumentos cppara corrigir esse erro.
Pausado até novo aviso.
1

Parece que você tem muitos *.jpgarquivos nesse diretório para colocá-los todos na linha de comando de uma só vez. Você poderia tentar:

find /home/ftpuser/public_html/ftparea1 -name '*.jpg' | xargs -I {} cp -uf {} /home/ftpuser/public_html/ftparea2/

Pode ser necessário verificar man xargssua implementação para ver se a -Iopção está correta para o seu sistema.

Na verdade, você realmente pretende copiar esses arquivos para o mesmo local em que eles já estão?

Greg Hewgill
fonte
desculpas Estes são dois diretórios diferentes deve ser ftpuser1 e ftpuser2
icelizard
Apenas tentei isso: ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} / / / / / Ainda tenho -bash casa ftpuser2 public_html ftparea: / ls / bin: lista de argumentos muito longa
icelizard
Ah, você está certo, é claro lsque terá o mesmo problema! Eu mudei para o findque não.
Greg Hewgill
0

Vá para a pasta

cd /home/ftpuser1/public_html/

e execute o seguinte:

cp -R ftparea/ /home/ftpuser2/public_html/

Dessa forma, se a pasta 'ftparea' tiver subpastas, isso poderá ser um efeito negativo se você quiser apenas os arquivos '* .jpg', mas se não houver subpastas, essa abordagem será definitivamente muito mais rápida do que usando find e xargs

pinpinokio
fonte