Memecahkan "mv: Daftar argumen terlalu panjang"?

64

Saya memiliki folder dengan lebih dari satu juta file yang perlu disortir, tetapi saya tidak dapat melakukan apa-apa karena mvmenampilkan pesan ini sepanjang waktu

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

Saya menggunakan perintah ini untuk memindahkan file ekstensi-kurang:

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

Jawaban:

82

xargsadalah alat untuk pekerjaan itu. Itu, atau finddengan -exec … {} +. Alat-alat ini menjalankan perintah beberapa kali, dengan sebanyak mungkin argumen yang dapat dilewati dalam sekali jalan.

Kedua metode lebih mudah dilakukan ketika daftar argumen variabel di akhir, yang tidak terjadi di sini: argumen terakhir mvadalah tujuannya. Dengan utilitas GNU (yaitu pada Linux yang tidak tertanam atau Cygwin), -topsi untuk mvberguna, untuk melewati tujuan terlebih dahulu.

Jika nama file tidak memiliki spasi putih atau apapun \"', maka Anda dapat memberikan nama file sebagai input xargs( echoperintahnya adalah bash builtin, sehingga tidak tunduk pada batas panjang baris perintah):

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

Anda dapat menggunakan -0opsi untuk xargsmenggunakan input yang dibatasi-nol alih-alih format kuotasi default.

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

Atau, Anda dapat membuat daftar nama file dengan find. Untuk menghindari pengulangan ke subdirektori, gunakan -type d -prune. Karena tidak ada tindakan yang ditentukan untuk file gambar yang terdaftar, hanya file lain yang dipindahkan.

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

(Ini termasuk file dot, tidak seperti metode wildcard shell.)

Jika Anda tidak memiliki utilitas GNU, Anda dapat menggunakan shell perantara untuk mendapatkan argumen dalam urutan yang benar. Metode ini berfungsi pada semua sistem POSIX.

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

Di zsh, Anda dapat memuat mvbuiltin :

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

atau jika Anda lebih suka membiarkan mvdan nama lain terus mengacu pada perintah eksternal:

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

atau dengan gumpalan ksh-style:

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

Atau, gunakan GNU mvdan zargs:

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/
Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Dua perintah pertama mengembalikan "-bash:!: Event not found" dan dua perintah berikutnya tidak memindahkan file sama sekali. Saya menggunakan CentOS 6.5 jika Anda harus tahu
Dominique
1
@Dominique Saya menggunakan sintaks globbing yang sama yang Anda gunakan dalam pertanyaan Anda. Anda harus shopt -s extglobmengaktifkannya. Saya melewatkan langkah dalam findperintah, saya sudah memperbaikinya.
Gilles 'SANGAT berhenti menjadi jahat'
Saya mendapatkan ini dengan perintah find "find: ekspresi tidak valid; Anda telah menggunakan operator biner '-o' tanpa apa-apa sebelumnya." Sekarang saya akan mencoba yang lain.
Dominique
@Dominique findPerintah - perintah yang saya posting (sekarang) berfungsi. Anda harus meninggalkan bagian ketika menyalin-menempel.
Gilles 'SO- stop being evil'
Gilles, untuk perintah find, mengapa tidak menggunakan operator "tidak" !,? Ini lebih eksplisit dan lebih mudah dipahami daripada trailing aneh -o. Misalnya,! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan
13

Jika bekerja dengan kernel Linux sudah cukup, Anda cukup melakukannya

ulimit -s 100000

itu akan berfungsi karena kernel Linux menyertakan tambalan sekitar 10 tahun yang lalu yang mengubah batas argumen berdasarkan ukuran tumpukan: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ komit /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Perbarui: Jika Anda merasa berani, Anda bisa mengatakannya

ulimit -s unlimited

dan Anda akan baik-baik saja dengan ekspansi shell apa pun selama Anda memiliki cukup RAM.

Mikko Rantalainen
sumber
Itu hack. Bagaimana Anda tahu apa yang mengatur batas stack? Ini juga memengaruhi proses lain yang dimulai pada sesi yang sama.
Kusalananda
1
Ya, ini hack. Sebagian besar waktu peretasan ini hanya terjadi sekali saja (seberapa sering Anda memindahkan file dalam jumlah besar secara manual?). Jika Anda yakin bahwa prosesnya tidak akan memakan semua RAM Anda, Anda dapat mengaturnya ulimit -s unlimiteddan itu akan berfungsi untuk file yang praktis tidak terbatas.
Mikko Rantalainen
Dengan ulimit -s unlimitedbatas baris perintah aktual adalah 2 ^ 31 atau 2 GB. ( MAX_ARG_STRLENdalam sumber kernel.)
Mikko Rantalainen
9

Batas kelalaian argumen sistem operasi tidak berlaku untuk ekspansi yang terjadi di dalam shell interpreter. Jadi selain menggunakan xargsatau find, kita bisa menggunakan shell loop untuk memecah pemrosesan menjadi mvperintah - perintah individual :

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

Ini hanya menggunakan fitur dan utilitas POSIX Shell Command Language. Satu garis ini lebih jelas dengan lekukan, dengan titik koma yang tidak perlu dihapus:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done
Kaz
sumber
Dengan lebih dari satu juta file, ini pada gilirannya akan menelurkan lebih dari satu juta mvproses, alih-alih hanya beberapa yang dibutuhkan menggunakan findsolusi POSIX @Gilles yang diposting. Dengan kata lain, cara ini menghasilkan banyak churn CPU yang tidak perlu.
CivFan
@CivFan Masalah lain adalah meyakinkan diri sendiri bahwa versi yang dimodifikasi setara dengan yang asli. Sangat mudah untuk melihat bahwa casepernyataan hasil *ekspansi untuk memfilter beberapa ekstensi setara dengan !(*.jpg|*.png|*.bmp)ekspresi asli . The findJawabannya sebenarnya tidak setara; turun ke subdirektori (saya tidak melihat -maxdepthpredikat).
Kaz
-name . -o -type d -prune -omelindungi dari turun ke sub-direktori. -maxdepthtampaknya tidak sesuai dengan POSIX, meskipun itu tidak disebutkan di findhalaman manual saya .
CivFan
Digulung kembali ke revisi 1. Pertanyaan tidak mengatakan apa pun tentang variabel sumber atau tujuan, jadi ini menambahkan jawaban yang tidak perlu.
Kaz
5

Untuk solusi yang lebih agresif daripada yang ditawarkan sebelumnya, tarik sumber kernel Anda dan edit include/linux/binfmts.h

Tingkatkan ukuran MAX_ARG_PAGESmenjadi sesuatu yang lebih besar dari 32. Ini meningkatkan jumlah memori yang akan diizinkan oleh kernel untuk argumen program, sehingga memungkinkan Anda menentukan mvatau rmperintah Anda untuk jutaan file atau apa pun yang Anda lakukan. Kompilasi ulang, instal, reboot.

WASPADALAH! Jika Anda mengatur ini terlalu besar untuk memori sistem Anda, dan kemudian jalankan perintah dengan banyak argumen BURUK HAL AKAN TERJADI! Berhati-hatilah dalam melakukan hal ini pada sistem multi-pengguna, ini membuat lebih mudah bagi pengguna jahat untuk menggunakan semua memori Anda!

Jika Anda tidak tahu cara mengkompilasi ulang dan menginstal ulang kernel Anda secara manual, mungkin yang terbaik adalah Anda berpura-pura jawaban ini tidak ada untuk saat ini.

Perkins
sumber
5

Solusi yang lebih sederhana menggunakan "$origin"/!(*.jpg|*.png|*.bmp)alih-alih blok tangkap:

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

Terima kasih kepada @Score_Under

Untuk skrip multi-baris Anda dapat melakukan hal berikut (perhatikan ;sebelum donedijatuhkan):

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

Untuk melakukan solusi yang lebih umum yang memindahkan semua file, Anda bisa melakukan satu-baris:

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

Yang terlihat seperti ini jika Anda melakukan lekukan:

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

Ini mengambil setiap file dalam asal dan memindahkannya satu per satu ke tujuan. Kutipan di sekitar $filediperlukan jika ada spasi atau karakter khusus lainnya dalam nama file.

Berikut adalah contoh metode ini yang bekerja dengan sempurna

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
Kucing putih
sumber
Anda bisa menggunakan sesuatu seperti gumpalan asli di for-loop untuk mendapatkan solusi yang lebih dekat dengan apa yang diminta.
Score_Under
Apa maksudmu glob asli?
Whitecat
Maaf kalau itu sedikit samar, saya mengacu pada glob dalam pertanyaan: !(*.jpg|*.png|*.bmp). Anda bisa menambahkan itu untuk for-loop Anda dengan globbing "$origin"/!(*.jpg|*.png|*.bmp)yang akan menghindari kebutuhan untuk saklar yang digunakan dalam jawaban Kaz dan menjaga tubuh sederhana for-loop.
Score_Under
Skor Luar Biasa. Saya memasukkan komentar Anda dan memperbarui jawaban saya.
Whitecat
3

Terkadang lebih mudah untuk hanya menulis skrip kecil, misalnya dengan Python:

import glob, shutil

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

Anda dapat mengatasi batasan itu saat masih menggunakan mvjika Anda tidak keberatan menjalankannya beberapa kali.

Anda dapat memindahkan bagian sekaligus. Katakanlah misalnya Anda memiliki daftar panjang nama file alfanumerik.

mv ./subdir/a* ./

Itu bekerja. Kemudian pukullah satu potongan besar lagi. Setelah beberapa bergerak, Anda bisa kembali menggunakanmv ./subdir/* ./

Kristian
sumber
0

Ini dua sen saya, tambahkan ini .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

Pemakaian

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Ako
sumber