Rozwiązywanie problemu „mv: lista argumentów za długa”?


64

Mam folder z ponad milionem plików, który wymaga sortowania, ale tak naprawdę nie mogę nic zrobić, ponieważ mvcały czas wyświetla ten komunikat

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

Używam tego polecenia do przenoszenia plików bez rozszerzeń:

mv -- !(*.jpg|*.png|*.bmp) targetdir/

Odpowiedzi:


82

xargsjest narzędziem do pracy. To lub findz -exec … {} +. Te narzędzia uruchamiają polecenie kilka razy, z tyloma argumentami, ile można przekazać za jednym razem.

Obie metody są łatwiejsze do przeprowadzenia, gdy lista argumentów zmiennych znajduje się na końcu, co nie ma miejsca w tym przypadku: ostatnim argumentem mvjest miejsce docelowe. W przypadku narzędzi GNU (tj. W niewbudowanym systemie Linux lub Cygwin) -topcja mvjest przydatna, aby najpierw przekazać miejsce docelowe.

Jeśli nazwy plików nie mają białych znaków ani żadnego z nich \"', możesz po prostu podać nazwy plików jako dane wejściowe xargs( echopolecenie jest wbudowane w bash, więc nie podlega limitowi długości wiersza poleceń):

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

Możesz użyć -0opcji, aby xargsużyć danych rozdzielanych znakami null zamiast domyślnego formatu cytowanego.

printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir

Alternatywnie możesz wygenerować listę nazw plików za pomocą find. Aby uniknąć rekurencji w podkatalogach, użyj -type d -prune. Ponieważ dla wymienionych plików obrazów nie określono żadnej akcji, przenoszone są tylko inne pliki.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +

(Obejmuje to pliki kropkowe, w przeciwieństwie do metod wieloznacznych powłoki).

Jeśli nie masz narzędzi GNU, możesz użyć powłoki pośredniej, aby uzyskać argumenty we właściwej kolejności. Ta metoda działa na wszystkich systemach POSIX.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +

W Zsh możesz załadować mvwbudowane :

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

lub jeśli wolisz pozwolić, mva inne nazwy nadal odnoszą się do poleceń zewnętrznych:

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

lub z globami w stylu ksh:

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

Alternatywnie, używając GNU mvi zargs:

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

1
Pierwsze dwa polecenia zwróciły „-bash:!: Nie znaleziono zdarzenia”, a następne dwa nie przeniosły żadnych plików. Jestem na CentOS 6.5, jeśli powinieneś wiedzieć
Dominique

1
@Dominique Użyłem tej samej składni globowania, której użyłeś w swoim pytaniu. Musisz shopt -s extglobto włączyć. Brakowało mi kroku w findpoleceniach, naprawiłem je.
Gilles

Dostaję to z poleceniem find „find: nieprawidłowe wyrażenie; użyłeś operatora binarnego„ -o ”bez niczego przed nim”. Spróbuję teraz innych.
Dominique

@Dominique findPolecenia, które opublikowałem (teraz) działają. Musisz wkleić część podczas wklejania kopii.
Gilles

Gilles, dla poleceń znalezienia, dlaczego nie korzystać z „nie” z operatorem, !? Jest to bardziej wyraźne i łatwiejsze do zrozumienia niż nieparzyste końcowe -o. Na przykład! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan

13

Jeśli praca z jądrem Linuksa wystarczy, możesz to zrobić

ulimit -s 100000

to zadziała, ponieważ jądro Linuksa zawierało łatkę około 10 lat temu, która zmieniła limit argumentów w zależności od wielkości stosu: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ commit /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Aktualizacja: jeśli czujesz się odważny, możesz powiedzieć

ulimit -s unlimited

i będziesz w porządku z dowolnymi rozszerzeniami powłoki, o ile masz wystarczającą ilość pamięci RAM.


To jest hack. Skąd wiedziałbyś, na co ustawić limit stosów? Wpływa to również na inne procesy rozpoczęte w tej samej sesji.
Kusalananda

1
Tak, to hack. Zazwyczaj tego rodzaju ataki hakerskie są jednorazowe (jak często ręcznie przenosisz ogromną liczbę plików?). Jeśli masz pewność, że proces nie zje całej pamięci RAM, możesz ustawić ulimit -s unlimitedi będzie działać dla praktycznie nieograniczonej liczby plików.
Mikko Rantalainen

Z ulimit -s unlimitedrzeczywistej granicy linii polecenia jest 2 ^ 31 lub 2 Gb. ( MAX_ARG_STRLENw źródle jądra.)
Mikko Rantalainen

9

Limit przekazywania argumentów systemu operacyjnego nie ma zastosowania do rozszerzeń, które mają miejsce w interpretatorze powłoki. Oprócz użycia xargslub find, możemy po prostu użyć pętli powłoki, aby rozbić przetwarzanie na poszczególne mvpolecenia:

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

Używa tylko funkcji i narzędzi POSIX Shell Command Language. Ten jednowarstwowy jest wyraźniejszy dzięki wcięciu, z usuniętymi niepotrzebnymi średnikami:

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

Z ponad milionem plików, to z kolei spowoduje odrodzenie ponad miliona mvprocesów, zamiast tylko kilku potrzebnych przy użyciu findrozwiązania POSIX opublikowanego przez @Gilles. Innymi słowy, w ten sposób powstaje wiele niepotrzebnych rezygnacji z procesora.
CivFan,

@CivFan Kolejnym problemem jest przekonanie siebie, że zmodyfikowana wersja jest odpowiednikiem oryginału. Łatwo zauważyć, że caseoświadczenie o wyniku *rozszerzenia w celu odfiltrowania kilku rozszerzeń jest równoważne oryginalnemu !(*.jpg|*.png|*.bmp)wyrażeniu. findOdpowiedź jest w rzeczywistości nie równoważnym; schodzi do podkatalogów (nie widzę -maxdepthpredykatu).
Kaz

-name . -o -type d -prune -ochroni przed zejściem do podkatalogów. -maxdepthnajwyraźniej nie jest zgodny z POSIX, chociaż nie jest to wspomniane na mojej findstronie man.
CivFan

Cofnięto do wersji 1. Pytanie nie mówi nic o zmiennych źródłowych lub docelowych, więc dodaje to niepotrzebnego pytania do odpowiedzi.
Kaz

5

Aby uzyskać bardziej agresywne rozwiązanie niż wcześniej oferowane, pobierz źródło jądra i edytuj include/linux/binfmts.h

Zwiększ rozmiar MAX_ARG_PAGESdo czegoś większego niż 32. Zwiększa to ilość pamięci, którą jądro pozwoli na argumenty programu, tym samym umożliwiając określenie twojego polecenia mvlub rmpolecenia dla miliona plików lub cokolwiek robisz. Ponownie skompiluj, zainstaluj, uruchom ponownie.

STRZEC SIĘ! Jeśli ustawisz zbyt dużą wartość dla pamięci systemowej, a następnie uruchom polecenie z dużą liczbą argumentów, BĘDĄ RZECZY! Zachowaj szczególną ostrożność, robiąc to w systemach z wieloma użytkownikami, dzięki czemu złośliwi użytkownicy mogą zużywać całą pamięć!

Jeśli nie wiesz, jak ręcznie skompilować i ponownie zainstalować jądro, prawdopodobnie najlepiej udajesz, że ta odpowiedź na razie nie istnieje.


5

Prostsze rozwiązanie wykorzystujące "$origin"/!(*.jpg|*.png|*.bmp)zamiast bloku catch:

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

Dzięki @Score_Under

W przypadku skryptu wielowierszowego możesz wykonać następujące czynności (zwróć uwagę ;przed doneupuszczeniem):

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

Aby zrobić bardziej ogólne rozwiązanie, które przenosi wszystkie pliki, możesz wykonać jedną linię:

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

Który wygląda tak, jeśli wykonasz wcięcie:

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

To bierze każdy plik w źródle i przenosi je jeden po drugim do miejsca docelowego. Cudzysłowy $filesą konieczne w przypadku, gdy w nazwach plików znajdują się spacje lub inne znaki specjalne.

Oto przykład tej metody, która działała idealnie

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done

Możesz użyć czegoś takiego jak oryginalny glob w pętli for, aby uzyskać bliższe rozwiązanie tego, o co jest proszony.
Score_Under

Co masz na myśli oryginalną glob?
Whitecat,

Przepraszam jeśli to było trochę tajemnicze, ja odnoszę się do glob w pytaniu: !(*.jpg|*.png|*.bmp). Możesz dodać to do swojej pętli for poprzez globbing, "$origin"/!(*.jpg|*.png|*.bmp)który pozwoliłby uniknąć konieczności użycia przełącznika użytego w odpowiedzi Kaz i zachować proste ciało pętli for.
Score_Under

Niesamowity wynik. Uwzględniłem twój komentarz i zaktualizowałem swoją odpowiedź.
Whitecat

3

Czasami najłatwiej jest po prostu napisać mały skrypt, np. W Pythonie:

import glob, shutil

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

1

Możesz obejść to ograniczenie, wciąż je wykorzystując, mvjeśli nie masz nic przeciwko uruchomieniu go kilka razy.

Możesz przenosić porcje na raz. Załóżmy na przykład, że masz długą listę alfanumerycznych nazw plików.

mv ./subdir/a* ./

To działa. Następnie wybij kolejny duży kawałek. Po kilku ruchach możesz po prostu wrócić do korzystaniamv ./subdir/* ./


0

Oto moje dwa centy, dodaj to do .bash_profile

mv() {
  if [[ -d $1 ]]; then #directory mv
    /bin/mv $1 $2
  elif [[ -f $1 ]]; then #file mv
    /bin/mv $1 $2
  else
    for f in $1
    do
      source_path=$f
      #echo $source_path
      source_file=${source_path##*/}
      #echo $source_file
      destination_path=${2%/} #get rid of trailing forward slash

      echo "Moving $f to $destination_path/$source_file"

      /bin/mv $f $destination_path/$source_file
    done
  fi
}
export -f mv

Stosowanie

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.