Błąd „Zbyt długa lista argumentów” podczas kopiowania dużej liczby plików

12

Używam następującego polecenia:

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

I pojawia się błąd:

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

Próbowałem też:

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

Nadal dostałem -bash: / bin / ls: Lista argumentów za długa

Jakieś pomysły?

icelizard
źródło
Próbuję skopiować wszystkie pliki JPG z jednego katalogu do innego, ale tylko nowe pliki i te, które zostały zaktualizowane.
icelizard
lsnie jest przeznaczony do tego rodzaju czynności. Zastosowanie find.
Wstrzymano do odwołania.
Problem nie dotyczy ls, lecz liczby argumentów przekazywanych przez powłokę do ls. Ten sam błąd wystąpiłby w przypadku vi lub dowolnego niewbudowanego polecenia.
Chris
Ale nie lsjest specjalnie zaprojektowany do tego: mywiki.wooledge.org/ParsingLs
Wstrzymany do odwołania.
To prawda, ale w tym przypadku błąd nie jest spowodowany błędem parsowania z ls, lecz przekazaniem miliarda argumentów do nowego procesu, którym jest ls. Oprócz niewłaściwego korzystania z ls, zdarza się również, że zderza się z ograniczeniami zasobów / projektowania Uniksa. W takim przypadku pacjent ma ból brzucha i złamaną nogę.
Chris

Odpowiedzi:

19

* .jpg rozwija się do listy dłużej niż jest w stanie obsłużyć powłokę. Spróbuj tego zamiast tego

find  /home/ftpuser/public_html/ftparea/ -name "*.jpg" -exec cp -uf "{}" /your/destination \;
Shawn Chin
źródło
Użyłem find / home / ftpuser1 / public_html / ftparea / -name "* jpg" -exec cp -uf "{}" / home / ftpuser2 / public_html / ftparea / i otrzymałem następujący błąd find: brak argumentu w `-exec '
icelizard,
Brakuje ostatniego argumentu CP, odpowiadający powiedział ci słusznie. Sprawdź dokładnie swoją implementację. Zauważ, że w tej odpowiedzi brakuje kropki w „* .jpg”, co może prowadzić do złych zachowań (np. Katalog o nazwie „myjpg”). Zauważ, że może to być paranoikalne, ale bezpieczniejsze jest dokładne określenie, co zamierzasz skopiować za pomocą pliku -type (zapobiegając wpływowi katalogów, dowiązań symbolicznych itp.)
drAlberT
Po bliższej inspekcji brakowało mi „\;” aby zakończyć polecenie, które -exec powinien wykonać. Głupi ja!
icelizard
@AlberT: dzięki za brakujące kropki. To była literówka. Odpowiedź zaktualizowana.
Shawn Chin,
To nie tak, że CP nie może sobie z tym poradzić. Powłoka nie może.
d -_- b
6

Istnieje maksymalny limit długości listy argumentów dla komend systemowych - limit ten jest specyficzny dla dystrybucji na podstawie wartości MAX_ARG_PAGESczasu kompilacji jądra i nie można go zmienić bez ponownej kompilacji jądra.

Ze względu na sposób, w jaki powłoka obsługuje globbing, wpłynie to na większość poleceń systemowych, gdy użyjesz tego samego argumentu („* .jpg”). Ponieważ glob jest najpierw przetwarzany przez powłokę, a następnie wysyłany do polecenia, polecenie:

cp -uf *.jpg /targetdir/

jest zasadniczo taki sam jak powłoka, tak jakbyś napisał:

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

Jeśli masz do czynienia z wieloma plikami JPEG, może to bardzo szybko stać się niemożliwe do opanowania. W zależności od konwencji nazewnictwa i liczby faktycznie przetwarzanych plików, możesz uruchomić polecenie cp w innym podzbiorze katalogu na raz:

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

Może to działać, ale dokładna skuteczność zależy od tego, jak dobrze możesz podzielić listę plików na wygodne globalne bloki.

Globalny. Lubię to słowo.

Niektóre polecenia, takie jak find i xargs , mogą obsługiwać duże listy plików bez tworzenia list argumentów o boleśnie dużych rozmiarach.

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

Argument -exec uruchomi resztę wiersza poleceń jeden raz dla każdego znalezionego pliku przez find , zastępując {} każdą znalezioną nazwą pliku. Ponieważ polecenie cp jest uruchamiane tylko na jednym pliku na raz, limit listy argumentów nie stanowi problemu.

Może to być powolne ze względu na konieczność przetwarzania każdego pliku osobno. Korzystanie z xargs może zapewnić bardziej wydajne rozwiązanie:

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

xargs może pobrać pełną listę plików dostarczoną przez find i podzielić ją na listy argumentów o zarządzalnych rozmiarach i uruchomić cp na każdej z tych podlist.

Oczywiście istnieje również możliwość ponownej kompilacji jądra, ustawiając większą wartość dla MAX_ARG_PAGES. Jednak rekompilacja jądra to więcej pracy, niż zamierzam wyjaśnić w tej odpowiedzi.

goldPseudo
źródło
Nie mam pojęcia, dlaczego zostało to odrzucone. To jedyna odpowiedź, która wydaje się wyjaśniać, dlaczego tak się dzieje. Może dlatego, że nie sugerujesz używania xargs jako optymalizacji?
Chris
dodano w rozwiązaniu xargs, ale nadal martwię się, że głosy negatywne wynikają z czegoś rażąco błędnego w moich szczegółach i nikt nie chce mi powiedzieć, co to jest. :(
goldPseudo
xargswydaje się być znacznie wydajniejszy, ponieważ wynikowa liczba wywołań poleceń jest znacznie mniejsza. W moim przypadku widzę 6-12-krotnie lepszą wydajność, gdy korzystam argswtedy, gdy zastosowanie -execrozwiązania z rosnącą liczbą plików zwiększa wydajność.
Jan Vlcinsky
3

Dzieje się tak, ponieważ *.jpgpo rozwinięciu wyrażenie wieloznaczne ( ) przekracza limit długości argumentów wiersza poleceń (prawdopodobnie dlatego, że masz dużo plików .jpg /home/ftpuser/public_html/ftparea).

Istnieje kilka sposobów obejścia tego ograniczenia, na przykład używanie findlub xargs. Zapoznaj się z tym artykułem, aby uzyskać więcej informacji na ten temat.

mfriedman
źródło
+1 za dobry zasób zewnętrzny na temat.
viam0Zah,
3

Jak skomentował GoldPseudo, istnieje ograniczenie liczby argumentów, które można przekazać procesowi, który spawnujesz. Zobacz jego odpowiedź na dobry opis tego parametru.

Możesz uniknąć problemu, albo nie przekazując procesowi zbyt wielu argumentów, albo zmniejszając liczbę przekazywanych argumentów.

Pętla for w powłoce, find i ls, grep i pętla while robią to samo w tej sytuacji -

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

i

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

i

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

wszystkie mają jeden program, który odczytuje katalog (sama powłoka, find i ls) i inny program, który faktycznie pobiera jeden argument na wykonanie i iteruje całą listę poleceń.

Teraz będzie to powolne, ponieważ rm musi być rozwidlony i wykonany dla każdego pliku, który pasuje do wzorca * .jpg.

W tym momencie pojawia się xargs. xargs pobiera standardowe dane wejściowe i dla każdego N (dla Freebsd domyślnie jest to 5000), spawnuje jeden program z N argumentami. xargs jest optymalizacją powyższych pętli, ponieważ wystarczy rozwidlić programy 1 / N, aby przejść do całego zestawu plików, które odczytują argumenty z wiersza poleceń.

Chris
źródło
1

Glob „*” rozszerza się do zbyt wielu nazw plików. Zamiast tego użyj find / home / ftpuser / public_html -name '* .jpg'.

William Pursell
źródło
Znajdź i echo * dają ten sam wynik - kluczem tutaj jest xargs, który nie przekazuje wszystkich 1 miliardów argumentów wiersza poleceń do polecenia, które powłoka próbuje rozwidlić.
Chris
echo * nie powiedzie się, jeśli jest zbyt wiele plików, ale odnajdzie się pomyślnie. Również użycie find -exec z + jest równoważne użyciu xargs. (Jednak nie wszyscy znajdują wsparcie +)
William Pursell
1

Korzystanie z +opcji find -execto znacznie przyspieszy operację.

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

+Opcja wymaga {}, aby być ostatni argument tak użyciem -t /your/destination(lub --target-directory=/your/destination) opcję cpsprawia, że pracują.

Od man find:

-exec polecenie {} +

          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.

Edycja : zmieniono argumenty na cp

Wstrzymano do odwołania.
źródło
Dostaję: brakujący argument do `-exec '/ home / ftpuser1 / public_html / ftparea / -name' * jpg '-exec cp -uf" {} "/ home / ftpuser2 / public_html / ftparea / +
icelizard
Zmieniłem argumenty, cpaby naprawić ten błąd.
Wstrzymano do odwołania.
1

Wygląda na to, że masz zbyt wiele *.jpgplików w tym katalogu, aby umieścić je wszystkie w wierszu polecenia jednocześnie. Możesz spróbować:

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

Konieczne może być sprawdzenie man xargsimplementacji, aby sprawdzić, czy -Iprzełącznik jest odpowiedni dla twojego systemu.

Czy naprawdę chcesz skopiować te pliki do tej samej lokalizacji, w której już się znajdują?

Greg Hewgill
źródło
przepraszam, to są dwa różne katalogi powinny być ftpuser1 i ftpuser2
icelizard
Właśnie spróbowałem: ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} / home / ftpuser2 / public_html / ftparea / Nadal dostałem -bash: / bin / ls: Lista argumentów za długa
icelizard
Och, masz całkowitą rację, oczywiście lsbędzie miał ten sam problem! Zmieniłem się na findco nie.
Greg Hewgill,
0

Idź do folderu

cd /home/ftpuser1/public_html/

i wykonaj następujące czynności:

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

W ten sposób, jeśli folder „ftparea” ma podfoldery, może to mieć negatywny efekt, jeśli chcesz z niego tylko pliki „* .jpg”, ale jeśli nie ma żadnych podfolderów, to podejście będzie zdecydowanie szybsze niż za pomocą find i xargs

pinpinokio
źródło