Giải quyết mv: Danh sách đối số quá dài?


64

Tôi có một thư mục chứa hơn một triệu tệp cần sắp xếp, nhưng tôi thực sự không thể làm gì được vì mvxuất ra thông báo này mọi lúc

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

Tôi đang sử dụng lệnh này để di chuyển các tệp không có phần mở rộng:

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

Câu trả lời:


82

xargslà công cụ cho công việc. Điều đó, hoặc findvới -exec … {} +. Các công cụ này chạy một lệnh nhiều lần, với càng nhiều đối số có thể được truyền trong một lần.

Cả hai phương thức đều dễ thực hiện hơn khi danh sách đối số biến ở cuối, không phải là trường hợp ở đây: đối số cuối cùng mvlà đích. Với các tiện ích GNU (tức là trên Linux hoặc Cygwin không được nhúng), -ttùy chọn mvnày rất hữu ích, để vượt qua đích trước.

Nếu tên tệp không có khoảng trắng cũng như bất kỳ tên nào \"', thì bạn chỉ cần cung cấp tên tệp làm đầu vào xargs( echolệnh là bash dựng sẵn, vì vậy nó không chịu giới hạn độ dài dòng lệnh):

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

Bạn có thể sử dụng -0tùy chọn để xargssử dụng đầu vào được phân tách bằng null thay vì định dạng được trích dẫn mặc định.

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

Ngoài ra, bạn có thể tạo danh sách tên tệp với find. Để tránh đệ quy vào các thư mục con, sử dụng -type d -prune. Vì không có hành động nào được chỉ định cho các tệp hình ảnh được liệt kê, chỉ các tệp khác được di chuyển.

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

(Điều này bao gồm các tệp chấm, không giống như các phương thức ký tự đại diện.)

Nếu bạn không có tiện ích GNU, bạn có thể sử dụng trình bao trung gian để nhận các đối số theo đúng thứ tự. Phương pháp này hoạt động trên tất cả các hệ thống POSIX.

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

Trong zsh, bạn có thể tải mvnội dung :

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

hoặc nếu bạn muốn cho phép mvvà các tên khác tiếp tục tham chiếu đến các lệnh bên ngoài:

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

hoặc với những quả cầu kiểu ksh:

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

Ngoài ra, sử dụng GNU mvzargs:

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

1
Hai lệnh đầu tiên trả về "-bash:!: Không tìm thấy sự kiện" và hai lệnh tiếp theo không di chuyển bất kỳ tệp nào cả. Tôi nên dùng CentOS 6.5 nếu bạn biết
Dominique

1
@Dominique Tôi đã sử dụng cú pháp toàn cầu giống như bạn đã sử dụng trong câu hỏi của mình. Bạn sẽ cần shopt -s extglobphải kích hoạt nó. Tôi đã bỏ lỡ một bước trong các findlệnh, tôi đã sửa chúng.
Gilles 'SO- ngừng trở nên xấu xa'

Tôi nhận được điều này với lệnh find "find: biểu thức không hợp lệ; bạn đã sử dụng toán tử nhị phân '-o' không có gì trước nó." Bây giờ tôi sẽ thử những cái khác.
Dominique

@Dominique Các findlệnh tôi đã đăng (bây giờ) hoạt động. Bạn phải bỏ đi một phần khi dán sao chép.
Gilles 'SO- ngừng trở nên xấu xa'

Gilles, đối với các lệnh find, tại sao không sử dụng toán tử "không" , !? Nó rõ ràng và dễ hiểu hơn so với dấu vết lẻ -o. Ví dụ:! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivilFan

13

Nếu làm việc với nhân Linux là đủ, bạn chỉ cần làm

ulimit -s 100000

điều đó sẽ hoạt động vì nhân Linux bao gồm một bản vá khoảng 10 năm trước đã thay đổi giới hạn đối số dựa trên kích thước ngăn xếp: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ cam kết /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Cập nhật: Nếu bạn cảm thấy dũng cảm, bạn có thể nói

ulimit -s unlimited

và bạn sẽ ổn với bất kỳ bản mở rộng vỏ nào miễn là bạn có đủ RAM.


Đó là một hack. Làm thế nào bạn biết những gì để thiết lập giới hạn ngăn xếp? Điều này cũng ảnh hưởng đến các quá trình khác bắt đầu trong cùng một phiên.
Kusalananda

1
Vâng, đó là một hack. Hầu hết thời gian các loại hack này là một lần (bạn có thường xuyên di chuyển số lượng lớn tệp theo cách thủ công không?). Nếu bạn chắc chắn rằng quy trình sẽ không ăn hết RAM của bạn, bạn có thể đặt ulimit -s unlimitedvà nó sẽ hoạt động cho các tệp thực tế không giới hạn.
Mikko Rantalainen

Với ulimit -s unlimitedgiới hạn dòng lệnh thực tế là 2 ^ 31 hoặc 2 GB. ( MAX_ARG_STRLENtrong nguồn nhân.)
Mikko Rantalainen

9

Giới hạn vượt qua đối số của hệ điều hành không áp dụng cho các mở rộng xảy ra trong trình thông dịch shell. Vì vậy, ngoài việc sử dụng xargshoặc find, chúng ta chỉ cần sử dụng một vòng lặp shell để chia quá trình xử lý thành các mvlệnh riêng lẻ :

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

Điều này chỉ sử dụng các tính năng và tiện ích của POSIX Shell Command Language. Lớp lót này rõ ràng hơn với vết lõm, với dấu chấm phẩy không cần thiết được loại bỏ:

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

Với hơn một triệu tệp, điều này sẽ lần lượt sinh ra hơn một triệu mvquy trình, thay vì chỉ một số ít cần thiết sử dụng findgiải pháp POSIX @Gilles được đăng. Nói cách khác, cách này dẫn đến rất nhiều CPU không cần thiết.
CivilFan

@CivFan Một vấn đề khác là tự thuyết phục bản thân rằng phiên bản sửa đổi tương đương với bản gốc. Thật dễ dàng để thấy rằng casetuyên bố về kết quả của *việc mở rộng để lọc ra một số tiện ích mở rộng tương đương với !(*.jpg|*.png|*.bmp)biểu thức ban đầu . Câu findtrả lời là trong thực tế không tương đương; nó đi xuống các thư mục con (tôi không thấy một -maxdepthvị ngữ).
Kaz

-name . -o -type d -prune -obảo vệ khỏi giảm dần vào thư mục con. -maxdepthrõ ràng là không tuân thủ POSIX, mặc dù điều đó không được đề cập trong findtrang người đàn ông của tôi .
CivilFan

Quay trở lại sửa đổi 1. Câu hỏi không nói gì về các biến nguồn hoặc biến đích, vì vậy điều này thêm phần không cần thiết vào câu trả lời.
Kaz

5

Đối với một giải pháp tích cực hơn những giải pháp được cung cấp trước đây, hãy kéo nguồn kernel của bạn lên và chỉnh sửa include/linux/binfmts.h

Tăng kích thước của MAX_ARG_PAGESmột cái gì đó lớn hơn 32. Điều này làm tăng dung lượng bộ nhớ mà hạt nhân sẽ cho phép đối số chương trình, do đó cho phép bạn chỉ định mvhoặc rmlệnh của bạn cho một triệu tệp hoặc bất cứ điều gì bạn đang làm. Biên dịch lại, cài đặt, khởi động lại.

HÃY THỬ! Nếu bạn đặt cái này quá lớn cho bộ nhớ hệ thống của bạn, và sau đó chạy một lệnh với rất nhiều đối số TRỞ LẠI NHỮNG ĐIỀU NÀY S H HẠNH PHÚC! Hãy cực kỳ thận trọng khi làm điều này với các hệ thống nhiều người dùng, nó giúp người dùng độc hại sử dụng hết bộ nhớ của bạn dễ dàng hơn!

Nếu bạn không biết cách biên dịch lại và cài đặt lại kernel của mình một cách thủ công, có lẽ tốt nhất là bạn chỉ giả vờ câu trả lời này không tồn tại cho đến bây giờ.


5

Một giải pháp đơn giản hơn bằng cách sử dụng "$origin"/!(*.jpg|*.png|*.bmp)thay vì khối bắt:

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

Cảm ơn @Score_Under

Đối với tập lệnh nhiều dòng, bạn có thể thực hiện các thao tác sau (chú ý ;trước khi donebỏ tập lệnh):

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

Để thực hiện một giải pháp tổng quát hơn giúp di chuyển tất cả các tệp, bạn có thể thực hiện một lớp lót:

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

Trông giống như thế này nếu bạn thụt lề:

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

Điều này sẽ đưa mọi tệp trong nguồn gốc và di chuyển từng cái một đến đích. Các trích dẫn xung quanh $filelà cần thiết trong trường hợp có khoảng trắng hoặc các ký tự đặc biệt khác trong tên tệp.

Dưới đây là một ví dụ về phương pháp này hoạt động hoàn hảo

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

Bạn có thể sử dụng một cái gì đó giống như quả cầu ban đầu trong vòng lặp for để có được giải pháp gần hơn với những gì được yêu cầu.
Điểm_Under

Bạn có ý nghĩa gì trên toàn cầu?
Whitecat

Xin lỗi nếu đó là một chút khó hiểu, tôi đã đề cập đến toàn cầu trong câu hỏi : !(*.jpg|*.png|*.bmp). Bạn có thể thêm nó vào vòng lặp for của mình bằng cách đặt vòng tròn "$origin"/!(*.jpg|*.png|*.bmp)để tránh sự cần thiết của công tắc được sử dụng trong câu trả lời của Kaz và giữ thân đơn giản của vòng lặp for.
Điểm_Under

Điểm tuyệt vời. Tôi kết hợp nhận xét của bạn và cập nhật câu trả lời của tôi.
Whitecat

3

Đôi khi, dễ nhất là chỉ viết một đoạn script nhỏ, ví dụ như trong Python:

import glob, shutil

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

1

Bạn có thể khắc phục hạn chế đó trong khi vẫn sử dụng mvnếu bạn không ngại chạy nó một vài lần.

Bạn có thể di chuyển các phần tại một thời điểm. Ví dụ, giả sử bạn có một danh sách dài các tên tệp chữ và số.

mv ./subdir/a* ./

Điều đó làm việc. Sau đó hạ gục một khúc lớn khác. Sau một vài lần di chuyển, bạn có thể quay lại sử dụngmv ./subdir/* ./


0

Đây là hai xu của tôi, nối nó vào .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

Sử dụng

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.