รายการอาร์กิวเมนต์ยาวเกินไปเมื่อคัดลอกไฟล์


26

ฉันเพิ่งถามคำถามเกี่ยวกับวิธีที่ฉันสามารถนับไฟล์ของส่วนขยายเฉพาะ ตอนนี้ผมต้องการไฟล์เหล่านี้ไปอยู่ที่ใหม่cpdir

ฉันกำลังพยายาม,

cp *.prj ../prjshp/

และ

cp * | grep '\.prj$' ../prjshp/

แต่พวกเขาให้ข้อผิดพลาดเดียวกัน

bash: / bin / cp: รายการอาร์กิวเมนต์ยาวเกินไป

ฉันจะคัดลอกพวกเขาได้อย่างไร


คำตอบ:


36

cp *.prj ../prjshp/เป็นคำสั่งที่ถูกต้อง แต่คุณได้พบกับกรณีที่หายากซึ่งมีขนาด จำกัด คำสั่งที่สองที่คุณพยายามไม่มีความหมาย

วิธีหนึ่งคือการเรียกใช้cpไฟล์ในกลุ่ม findคำสั่งรู้วิธีที่จะทำเช่นนี้:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find สำรวจไดเรกทอรีปัจจุบันและไดเรกทอรีด้านล่างซ้ำ
  • -maxdepth 1 หมายถึงการหยุดที่ระดับความลึก 1 คือไม่เรียกเก็บเงินในไดเรกทอรีย่อย
  • -name '*.prj'หมายถึงการกระทำเฉพาะกับไฟล์ที่มีชื่อตรงกับรูปแบบที่ระบุ สังเกตเครื่องหมายคำพูดรอบ ๆ รูปแบบ: มันจะถูกตีความโดยfindคำสั่งไม่ใช่โดยเชลล์
  • -exec … {} +หมายถึงการรันคำสั่งที่ระบุสำหรับไฟล์ทั้งหมด มันเรียกใช้คำสั่งหลาย ๆ ครั้งหากจำเป็นต้องระวังอย่าให้เกินขีด จำกัด บรรทัดคำสั่ง
  • mv -t ../prjshpย้ายไฟล์ที่ระบุไป../prjshpยัง -tตัวเลือกที่จะใช้ที่นี่เพราะข้อ จำกัด ของการfindสั่ง: ไฟล์ที่พบ (สัญลักษณ์{}) จะถูกส่งผ่านเป็นอาร์กิวเมนต์สุดท้ายของคำสั่งคุณไม่สามารถเพิ่มปลายทางหลังจากที่มัน

rsyncอีกวิธีหนึ่งคือการใช้งาน

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshpคัดลอกไดเรกทอรีปัจจุบันไป../prjshpซ้ำ
  • --include='*.prj' --exclude='*'หมายถึงการคัดลอกไฟล์ที่ตรงกัน*.prjและไม่รวมทุกอย่าง (รวมถึงไดเรกทอรีย่อยดังนั้น.prjจะไม่พบไฟล์ในไดเรกทอรีย่อย)

3
rsync โดยไกลทางออกที่ง่ายที่สุดที่นี่
ntk4

เพื่อให้เป็นที่ค่อนข้าง nitpicky คำสั่งที่สองcp * | grep '\.prj$' ../prjshp/ ไม่สมเหตุสมผล แต่สามารถใช้ได้ syntactically ถ้า*ขยายไปยังรายการของไฟล์ที่มีคนสุดท้ายเป็นไดเรกทอรี (aka cp SOURCE1 SOURCE2....DEST) ไปป์ไม่สมเหตุสมผลใด ๆ แน่ใจ แต่ยังคงถูกต้อง syntactically ตราบเชลล์เกี่ยวข้อง - มันจะdup()อธิบายไฟล์ได้ดีมันเป็นเพียงที่ปลายอ่านของท่อจะไม่ได้รับข้อมูลใด ๆ เพราะcpไม่ได้เขียนใด ๆ .
Sergiy Kolodyazhnyy

ทั้ง find และ rsync สร้างรายการอาร์กิวเมนต์เดียวกันผิดพลาดนานเกินไปสำหรับฉัน ห่วงสำหรับการแก้ปัญหาที่ง่ายที่สุดคือ
Meezaan-ud-Din

rsync แน่นอนเป็นวิธีที่จะทำการคัดลอกจำนวนมากแม้ว่าฉันจะนิ่งงันว่าเรามาพร้อมกับ Linux และเรามีข้อบกพร่อง / ข้อผิดพลาดแบบนี้และใช่ฉันจะคิดว่ามันเป็นข้อบกพร่อง / ข้อผิดพลาด
MitchellK

22

คำสั่งนี้คัดลอกไฟล์ทีละไฟล์และจะทำงานแม้ว่าจะมีไฟล์มากเกินกว่าที่*จะขยายเป็นcpคำสั่งเดียว:

for i in *; do cp "$i" ../prjshp/; done

มันใช้งานได้สำหรับฉัน
1rq3fea324wre

1
ง่ายและมีประสิทธิภาพ ฉันมีปัญหาคล้ายกันในการลบ ~ 1/4 ล้าน jpegs ที่ฉันได้ตัดตอนมาจากวิดีโอสำหรับโครงการ นี่คือวิธีที่ฉันใช้
Elder Geek

5

มีประเด็นสำคัญ 3 ข้อที่ควรคำนึงถึงเมื่อพบArgument list too longข้อผิดพลาด:

  • ความยาวของอาร์กิวเมนต์บรรทัดคำสั่งถูก จำกัด โดยARG_MAXตัวแปรซึ่งตามนิยาม POSIXคือ "... [m] ความยาว aximum ของการโต้แย้งไปยังฟังก์ชั่น execรวมถึงข้อมูลสภาพแวดล้อม" (เน้นเพิ่ม) "นั่นคือเมื่อเชลล์ดำเนินการไม่ -built-it คำสั่งจะต้องเรียกหนึ่งในexec()การวางไข่กระบวนการของคำสั่งนั้นและนั่นคือสิ่งที่ARG_MAXเข้ามาเล่นนอกจากนี้ชื่อหรือเส้นทางไปยังคำสั่งตัวเอง (ตัวอย่างเช่น/bin/echo) มีบทบาท

  • คำสั่งในตัวของเชลล์ดำเนินการโดยเชลล์ซึ่งหมายความว่าเชลล์ไม่ได้ใช้exec()ตระกูลฟังก์ชันดังนั้นจึงไม่ได้รับผลกระทบจากARG_MAXตัวแปร

  • คำสั่งบางอย่างเช่นxargsและfindตระหนักถึงARG_MAXตัวแปรและดำเนินการซ้ำ ๆ ภายใต้ขีด จำกัด นั้น

จากจุดด้านบนและดังที่แสดงในคำตอบที่ยอดเยี่ยมของ Kusalanandaสำหรับคำถามที่เกี่ยวข้องสิ่งArgument list too longนี้สามารถเกิดขึ้นได้เมื่อสภาพแวดล้อมมีขนาดใหญ่ ดังนั้นเมื่อคำนึงถึงสภาพแวดล้อมของผู้ใช้แต่ละคนอาจแตกต่างกันไปและขนาดอาร์กิวเมนต์เป็นไบต์มีความเกี่ยวข้องจึงยากที่จะสร้างไฟล์ / อาร์กิวเมนต์จำนวนเดียว

วิธีจัดการกับข้อผิดพลาดดังกล่าว?

สิ่งสำคัญคือต้องไม่ให้ความสำคัญกับจำนวนไฟล์ แต่เน้นว่าคำสั่งที่คุณจะใช้นั้นเกี่ยวข้องกับexec()ตระกูลของฟังก์ชันหรือไม่ซึ่งก็คือพื้นที่สแต็ก

ใช้เชลล์บิวด์อิน

ตามที่กล่าวไว้ก่อนหน้านี้ตัวบิวด์อินมีภูมิคุ้มกันARG_MAXจำกัด นั่นคือสิ่งต่าง ๆ เช่นforลูwhileปลูปบิวด์อินechoและบิวด์อินprintf- ทั้งหมดจะทำงานได้ดีพอ

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

ในคำถามที่เกี่ยวข้องกับการลบไฟล์มีวิธีแก้ไขดังนี้:

printf '%s\0' *.jpg | xargs -0 rm --

โปรดทราบว่านี่ใช้เชลล์ในprintfตัว หากเราโทรหาภายนอกprintfสิ่งนั้นจะเกี่ยวข้องexec()ด้วยดังนั้นจะล้มเหลวด้วยการโต้แย้งจำนวนมาก:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

ทุบตีอาร์เรย์

ตามคำตอบของ jlliagre bashไม่ได้กำหนดขอบเขตในอาร์เรย์ดังนั้นการสร้างอาร์เรย์ของชื่อไฟล์และการใช้ชิ้นต่อการวนซ้ำสามารถทำได้เช่นกันดังแสดงในคำตอบของ danjpreron :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

อย่างไรก็ตามสิ่งนี้มีข้อ จำกัด ในการเป็น bash-specific และไม่ใช่ POSIX

เพิ่มพื้นที่สแต็ก

บางครั้งคุณสามารถเห็นคนแนะนำให้เพิ่มพื้นที่สแต็คที่มีulimit -s <NUM>; บนค่า Linux ARG_MAX คือ 1 / 4th ของพื้นที่สแต็กสำหรับแต่ละโปรแกรมซึ่งหมายถึงการเพิ่มพื้นที่สแต็กเพิ่มสัดส่วนพื้นที่สำหรับอาร์กิวเมนต์

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

ตามคำตอบของ Franck Dernoncourtซึ่งอ้างถึง Linux Journal เรายังสามารถคอมไพล์เคอร์เนลลินุกซ์ที่มีค่ามากกว่าสำหรับหน้าหน่วยความจำสูงสุดสำหรับการโต้แย้งอย่างไรก็ตามมันทำงานได้ดีเกินความจำเป็นและเปิดโอกาสในการหาประโยชน์ตามที่ระบุไว้ในบทความ

หลีกเลี่ยงเปลือก

อีกวิธีหนึ่งคือการใช้งานpythonหรือpython3ที่มาพร้อมกับ Ubuntu python + here-docตัวอย่างด้านล่างเป็นสิ่งที่ฉันใช้เพื่อคัดลอกไดเรกทอรีขนาดใหญ่ของไฟล์บางแห่งในช่วง 40,000 รายการ:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

สำหรับ traversals recursive คุณสามารถใช้os.walk

ดูสิ่งนี้ด้วย:


2

IMHO เครื่องมือที่ดีที่สุดในการจัดการกับพยุหะของไฟล์และfind ดูxargs ดูman find ด้วยสวิตช์จะสร้างรายการชื่อไฟล์ที่แยกกัน (ชื่อไฟล์อาจมีอักขระตัวใดตัวหนึ่งหรือ) ที่เข้าใจโดยใช้สวิตช์ จากนั้นสร้างคำสั่งที่ยาวที่สุดที่อนุญาต (ชื่อไฟล์ส่วนใหญ่ไม่มีชื่อไฟล์ครึ่งท้าย) และเรียกใช้งาน ทำซ้ำสิ่งนี้จนกว่าจะไม่มีไฟล์ชื่ออีกต่อไป วิ่งเพื่อดูขีด จำกัดman xargsfind-print0NULNUL/xargs-0xargsxargsfindxargs --show-limits </dev/null

เพื่อแก้ปัญหาของคุณ (และหลังจากตรวจสอบman cpเพื่อค้นหา--target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.