ฉันมีโฟลเดอร์ที่มีไฟล์มากกว่าล้านไฟล์ที่ต้องการเรียงลำดับ แต่ฉันไม่สามารถทำอะไรได้เลยเพราะmv
แสดงข้อความนี้ตลอดเวลา
-bash: /bin/mv: Argument list too long
ฉันใช้คำสั่งนี้เพื่อย้ายไฟล์ส่วนขยายที่น้อยกว่า:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
ฉันมีโฟลเดอร์ที่มีไฟล์มากกว่าล้านไฟล์ที่ต้องการเรียงลำดับ แต่ฉันไม่สามารถทำอะไรได้เลยเพราะmv
แสดงข้อความนี้ตลอดเวลา
-bash: /bin/mv: Argument list too long
ฉันใช้คำสั่งนี้เพื่อย้ายไฟล์ส่วนขยายที่น้อยกว่า:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
คำตอบ:
xargs
เป็นเครื่องมือสำหรับงาน นั้นหรือกับfind
-exec … {} +
เครื่องมือเหล่านี้เรียกใช้คำสั่งหลายครั้งโดยมีอาร์กิวเมนต์มากที่สุดเท่าที่สามารถส่งผ่านได้ในครั้งเดียว
วิธีการทั้งสองจะง่ายต่อการดำเนินการเมื่อรายการอาร์กิวเมนต์ตัวแปรอยู่ที่ท้ายซึ่งไม่ใช่กรณีที่นี่: อาร์กิวเมนต์สุดท้ายที่mv
เป็นปลายทาง ด้วยโปรแกรมอรรถประโยชน์ของ GNU (เช่นบน Linux หรือ Cygwin ที่ไม่ได้ฝังตัว) -t
ตัวเลือกที่mv
มีประโยชน์คือการส่งผ่านปลายทางก่อน
หากชื่อไฟล์ไม่มีช่องว่างหรือส่วนใด ๆ\"'
คุณสามารถระบุชื่อไฟล์เป็นอินพุตxargs
( echo
คำสั่งคือ bash builtin ดังนั้นจึงไม่ขึ้นอยู่กับขีดจำกัดความยาวบรรทัดคำสั่ง):
echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir
คุณสามารถใช้-0
ตัวเลือกเพื่อxargs
ใช้อินพุตที่คั่นด้วย null แทนรูปแบบที่ยกมาเป็นค่าเริ่มต้น
printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir
find
หรือคุณสามารถสร้างรายชื่อไฟล์ที่มี เพื่อหลีกเลี่ยงการเข้าไปในไดเรกทอรีย่อย recursing -type d -prune
ใช้ เนื่องจากไม่ได้ระบุการดำเนินการสำหรับไฟล์รูปภาพที่แสดงรายการเฉพาะไฟล์อื่นเท่านั้นที่จะถูกย้าย
find . -name . -o -type d -prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec mv -t targetdir/ {} +
(ซึ่งรวมถึงไฟล์ dot ซึ่งไม่เหมือนวิธีเชลล์ wildcard)
หากคุณไม่มียูทิลิตี้ GNU คุณสามารถใช้เชลล์ระดับกลางเพื่อรับอาร์กิวเมนต์ตามลำดับที่ถูกต้อง วิธีนี้ใช้ได้กับทุกระบบ POSIX
find . -name . -o -type d -prune -o \
-name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
-exec sh -c 'mv "$@" "$0"' targetdir/ {} +
ใน zsh คุณสามารถโหลดmv
builtin ได้ :
setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/
หรือถ้าคุณต้องการที่จะให้mv
และชื่ออื่น ๆ ยังคงอ้างอิงถึงคำสั่งภายนอก:
setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/
หรือกับ ksh- สไตล์ globs:
setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/
อีกทางเลือกหนึ่งโดยใช้ GNU mv
และzargs
:
autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/
shopt -s extglob
เปิดใช้งาน ฉันพลาดขั้นตอนในfind
คำสั่งฉันได้แก้ไขแล้ว
find
คำสั่งที่ฉันโพสต์ (ตอนนี้) ทำงาน คุณต้องออกไปเป็นส่วนหนึ่งเมื่อคัดลอกวาง
!
? -o
มันมากขึ้นอย่างชัดเจนและง่ายต่อการเข้าใจกว่าท้ายแปลก ตัวอย่างเช่น! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
หากทำงานกับเคอร์เนลของ Linux ก็เพียงพอแล้วที่คุณจะทำได้
ulimit -s 100000
ที่จะทำงานได้เพราะเคอร์เนล Linux รวมแพตช์ประมาณ 10 ปีที่แล้วว่าข้อ จำกัด การเปลี่ยนแปลงจะขึ้นอยู่กับขนาดสแต็ก: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ กระทำ /? id = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba
อัปเดต: หากคุณรู้สึกกล้าคุณสามารถพูดได้
ulimit -s unlimited
และคุณจะใช้ได้กับการขยายเชลล์ใด ๆ ตราบใดที่คุณมี RAM เพียงพอ
ulimit -s unlimited
และมันจะทำงานกับไฟล์ไม่ จำกัด จริง
ulimit -s unlimited
ขีด จำกัด บรรทัดคำสั่งจริงคือ 2 ^ 31 หรือ 2 GB ( MAX_ARG_STRLEN
ในเคอร์เนลซอร์ส)
ข้อ จำกัด การส่งผ่านอาร์กิวเมนต์ของระบบปฏิบัติการใช้ไม่ได้กับการขยายที่เกิดขึ้นภายในตัวแปลเชลล์ ดังนั้นนอกเหนือจากการใช้xargs
หรือfind
เราสามารถใช้ shell loop เพื่อแยกการประมวลผลออกเป็นแต่ละmv
คำสั่ง:
for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done
สิ่งนี้ใช้คุณสมบัติและยูทิลิตีภาษาคำสั่ง POSIX Shell เท่านั้น ซับเดี่ยวนี้ชัดเจนยิ่งขึ้นพร้อมการเยื้องโดยลบเครื่องหมายอัฒภาคที่ไม่จำเป็นออก:
for x in *; do
case "$x" in
*.jpg|*.png|*.bmp)
;; # nothing
*) # catch-all case
mv -- "$x" target
;;
esac
done
mv
โพรเซสแทนที่จะเป็นเพียงส่วนน้อยที่จำเป็นโดยใช้find
โซลูชันPOSIX @Gilles ที่โพสต์ กล่าวอีกนัยหนึ่งวิธีนี้ส่งผลให้เกิดการสับ CPU ที่ไม่จำเป็นจำนวนมาก
case
คำแถลงเกี่ยวกับผลของ*
การขยายตัวเพื่อกรองส่วนขยายต่าง ๆ นั้นเทียบเท่ากับ!(*.jpg|*.png|*.bmp)
นิพจน์ดั้งเดิม find
คำตอบคือในความเป็นจริงไม่ได้เทียบเท่า มันลงไปในไดเรกทอรีย่อย (ฉันไม่เห็น-maxdepth
คำกริยา)
-name . -o -type d -prune -o
ปกป้องจากมากไปน้อยลงในไดเรกทอรีย่อย -maxdepth
เห็นได้ชัดว่าไม่ได้เป็นไปตาม POSIX แต่ที่ไม่ได้กล่าวถึงในfind
หน้าคนของฉัน
สำหรับวิธีการแก้ปัญหาที่ก้าวร้าวมากกว่าที่เสนอไว้ก่อนหน้าให้ดึงซอร์สเคอร์เนลและแก้ไข include/linux/binfmts.h
เพิ่มขนาดของMAX_ARG_PAGES
สิ่งที่มีขนาดใหญ่กว่า 32 ซึ่งจะเป็นการเพิ่มจำนวนหน่วยความจำที่เคอร์เนลจะอนุญาตให้มีการขัดแย้งกับโปรแกรมดังนั้นช่วยให้คุณสามารถระบุmv
หรือrm
คำสั่งของคุณสำหรับไฟล์นับล้านหรือสิ่งที่คุณกำลังทำ คอมไพล์ติดตั้งรีบูต
ระวัง! หากคุณตั้งค่านี้มีขนาดใหญ่เกินไปสำหรับหน่วยความจำระบบของคุณแล้วเรียกใช้คำสั่งที่มีอาร์กิวเมนต์มากมายสิ่งที่ไม่ดีจะเกิดขึ้น! ใช้ความระมัดระวังอย่างมากกับระบบที่มีผู้ใช้หลายคนทำให้ผู้ใช้ที่เป็นอันตรายสามารถใช้หน่วยความจำทั้งหมดของคุณได้ง่ายขึ้น!
หากคุณไม่ทราบวิธีการคอมไพล์ใหม่และติดตั้งเคอร์เนลใหม่ด้วยตนเองอาจเป็นไปได้ว่าคุณอาจต้องตอบคำถามนี้ในตอนนี้
วิธีแก้ปัญหาที่ง่ายขึ้นที่ใช้"$origin"/!(*.jpg|*.png|*.bmp)
แทน catch catch:
for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done
ขอบคุณ @Score_Under
สำหรับสคริปต์ที่มีหลายบรรทัดคุณสามารถทำสิ่งต่อไปนี้ (สังเกต;
ก่อนที่done
จะดร็อป):
for file in "$origin"/!(*.jpg|*.png|*.bmp); do # don't copy types *.jpg|*.png|*.bmp
mv -- "$file" "$destination"
done
หากต้องการแก้ปัญหาที่ทำให้เป็นลักษณะทั่วไปมากขึ้นที่จะย้ายไฟล์ทั้งหมดคุณสามารถทำสิ่งต่อไปนี้:
for file in "$origin"/*; do mv -- "$file" "$destination" ; done
ซึ่งมีลักษณะเช่นนี้หากคุณเยื้อง:
for file in "$origin"/*; do
mv -- "$file" "$destination"
done
การดำเนินการนี้จะนำทุกไฟล์มาและย้ายไปทีละไฟล์ $file
จำเป็นต้องใช้เครื่องหมายอัญประกาศล้อมรอบในกรณีที่มีช่องว่างหรืออักขระพิเศษอื่น ๆ ในชื่อไฟล์
นี่คือตัวอย่างของวิธีการนี้ที่ทำงานได้อย่างสมบูรณ์
for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
!(*.jpg|*.png|*.bmp)
ในคำถาม: คุณสามารถเพิ่มสิ่งนั้นลงใน for for loop ของคุณโดยการวนรอบ"$origin"/!(*.jpg|*.png|*.bmp)
ซึ่งจะหลีกเลี่ยงความต้องการสวิตช์ที่ใช้ในคำตอบของ Kaz และรักษาร่างกายที่เรียบง่ายของ for-loop
บางครั้งการเขียนสคริปต์เพียงเล็กน้อยง่ายที่สุดเช่นใน Python:
import glob, shutil
for i in glob.glob('*.jpg'):
shutil.move(i, 'new_dir/' + i)
คุณสามารถหลีกเลี่ยงข้อ จำกัด ดังกล่าวได้ในขณะที่ยังใช้อยู่mv
หากคุณไม่คิดที่จะเรียกใช้สองสามครั้ง
คุณสามารถย้ายบางส่วนได้ตลอดเวลา สมมติว่าคุณมีรายชื่อไฟล์ตัวอักษรและตัวเลขที่มีความยาว
mv ./subdir/a* ./
ที่ได้ผล จากนั้นเคาะก้อนใหญ่อีกก้อน หลังจากสองสามย้ายคุณสามารถกลับไปใช้mv ./subdir/* ./
นี่คือสองเซ็นต์ของฉันต่อท้ายนี้ .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
การใช้
mv '*.jpg' ./destination/
mv '/path/*' ./destination/