ฉันมีไฟล์หลายไฟล์ที่เรียงลำดับโดยชื่อไฟล์ในไดเรกทอรี ฉันต้องการคัดลอกไฟล์ N สุดท้าย (พูด, N = 4) ไปยังโฮมไดเร็กตอรี่ของฉัน ฉันควรทำอย่างไร
cp ./<the final 4 files> ~/
ฉันมีไฟล์หลายไฟล์ที่เรียงลำดับโดยชื่อไฟล์ในไดเรกทอรี ฉันต้องการคัดลอกไฟล์ N สุดท้าย (พูด, N = 4) ไปยังโฮมไดเร็กตอรี่ของฉัน ฉันควรทำอย่างไร
cp ./<the final 4 files> ~/
คำตอบ:
สามารถทำได้อย่างง่ายดายด้วยอาร์เรย์ bash / ksh93 / zsh:
a=(*)
cp -- "${a[@]: -4}" ~/
วิธีนี้ใช้ได้กับชื่อไฟล์ที่ไม่ได้ซ่อนไว้ทั้งหมดแม้ว่าจะมีช่องว่างแท็บบรรทัดใหม่หรืออักขระยากอื่น ๆ (สมมติว่ามีไฟล์ที่ไม่ได้ซ่อนอย่างน้อย 4 ไฟล์ในไดเรกทอรีปัจจุบันด้วยbash)
a=(*)
สิ่งนี้จะสร้างอาร์เรย์ที่aมีชื่อไฟล์ทั้งหมด ชื่อไฟล์ที่ส่งคืนโดย bash จะถูกจัดเรียงตามตัวอักษร (ฉันคิดว่านี่คือสิ่งที่คุณหมายถึงโดย"สั่งโดยชื่อไฟล์" )
${a[@]: -4}
สิ่งนี้จะคืนค่าองค์ประกอบสี่รายการสุดท้ายของอาร์เรย์a(หากอาร์เรย์มีองค์ประกอบอย่างน้อย 4 รายการด้วยbash)
cp -- "${a[@]: -4}" ~/
สิ่งนี้จะคัดลอกชื่อไฟล์สี่ไฟล์สุดท้ายไปยังไดเรกทอรีบ้านของคุณ
สิ่งนี้จะคัดลอกสี่ไฟล์ล่าสุดไปยังโฮมไดเร็กตอรี่และในเวลาเดียวกัน, เตรียมสตริงa_เป็นชื่อของไฟล์ที่คัดลอก:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
หากเราใช้a=(./some_dir/*)แทนa=(*)เราจะมีปัญหาของไดเรกทอรีที่แนบมากับชื่อไฟล์ ทางออกหนึ่งคือ:
a=(./some_dir/*)
for f in "${a[@]: -4}"; do cp "$f" ~/a_"${f##*/}"; done
โซลูชันอื่นคือใช้ subshell และcdไปยังไดเรกทอรีใน subshell:
(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done)
เมื่อเชลล์ย่อยเสร็จสมบูรณ์เชลล์จะส่งเรากลับไปที่ไดเรกทอรีดั้งเดิม
คำถามจะถามหาไฟล์ "เรียงตามชื่อไฟล์" คำสั่งนั้น Olivier Dulac ชี้ให้เห็นในความคิดเห็นจะแตกต่างจากสถานที่หนึ่งไปยังอีก ถ้ามันเป็นสิ่งสำคัญที่มีผลคงเป็นอิสระจากการตั้งค่าเครื่องแล้วมันจะดีที่สุดที่จะระบุสถานที่เกิดเหตุอย่างชัดเจนเมื่ออาร์เรย์aมีการกำหนด ตัวอย่างเช่น:
LC_ALL=C a=(*)
คุณสามารถค้นหาสถานที่ที่คุณอยู่ในขณะนี้โดยการเรียกใช้localeคำสั่ง
หากคุณกำลังใช้zshคุณสามารถใส่วงเล็บใน()รายการที่เรียกว่าตัวระบุแบบกลมซึ่งเลือกไฟล์ที่ต้องการ ในกรณีของคุณที่จะเป็น
cp *(On[1,4]) ~/
ที่นี่จะOnเรียงลำดับชื่อไฟล์ตามลำดับตัวอักษรตามลำดับย้อนหลังและ[1,4]ใช้เวลาเพียง 4 อันดับแรกเท่านั้น
คุณสามารถทำให้มีประสิทธิภาพมากขึ้นโดยการเลือกเฉพาะไฟล์ธรรมดา (ไม่รวมไดเรกทอรีท่อ ฯลฯ ) ด้วย.และยังโดยการผนวก--การcpคำสั่งเพื่อรักษาไฟล์ที่ชื่อขึ้นต้นด้วย-อย่างถูกต้องเพื่อ:
cp -- *(.On[1,4]) ~
เพิ่มDqualifier หากคุณต้องการพิจารณาไฟล์ที่ซ่อนอยู่ (dot-files):
cp -- *(D.On[1,4]) ~
*([-4,-1]).
on) เป็นพฤติกรรมเริ่มต้นดังนั้นจึงไม่จำเป็นต้องระบุสิ่งนี้และ-ด้านหน้าของตัวเลขจะนับชื่อไฟล์จากด้านล่าง
นี่คือวิธีการแก้ปัญหาโดยใช้คำสั่งทุบตีง่ายมาก
find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
คำอธิบาย:
find . -maxdepth 1 -type f
ค้นหาไฟล์ทั้งหมดในไดเรกทอรีปัจจุบัน
sort
เรียงลำดับตัวอักษร
tail -n 4
แสดง 4 บรรทัดสุดท้ายเท่านั้น
while read -r file; do cp "$file" ~/; done
วนซ้ำแต่ละบรรทัดที่ทำการดำเนินการคำสั่งคัดลอก
ตราบใดที่คุณพบว่าการเรียงลำดับของเชลล์น่าพอใจคุณสามารถทำได้:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
นี่คล้ายกับbashโซลูชันอาร์เรย์เฉพาะที่เสนอ แต่ควรพกพาไปยังเชลล์ที่รองรับ POSIX หมายเหตุบางประการเกี่ยวกับวิธีการทั้งสอง:
มันเป็นสิ่งสำคัญที่คุณจะนำcpข้อโต้แย้งของคุณด้วยcp --หรือคุณจะได้หนึ่งใน.จุดหรือ/ที่หัวของแต่ละชื่อ หากคุณล้มเหลวในการทำเช่นนี้คุณอาจเสี่ยงต่อการเป็นผู้นำ-ในการcpโต้แย้งแรกซึ่งสามารถตีความได้ว่าเป็นตัวเลือกและทำให้การดำเนินการล้มเหลวหรือแสดงผลลัพธ์ที่ไม่ได้ตั้งใจ
set -- ./*array=(./*)มันเป็นสิ่งสำคัญเมื่อใช้ทั้งสองวิธีเพื่อให้แน่ใจว่าคุณมีรายการอย่างน้อยมากในอาร์จีอาร์ของคุณในขณะที่คุณพยายามลบ - ฉันทำอย่างนั้นกับการขยายคณิตศาสตร์ สิ่งshiftเดียวที่เกิดขึ้นหากมีอย่างน้อย 4 รายการในอาเรย์อาร์ค - และจากนั้นเลื่อนเฉพาะส่วนแรกที่ทำให้ส่วนเกินของ 4 ..
set 1 2 3 4 5กรณีที่มันจะเลื่อน1ออกไป แต่ในset 1 2 3กรณีที่จะเปลี่ยนอะไรa=(1 2 3); echo "${a[@]: -4}"พิมพ์บรรทัดว่างหากคุณกำลังคัดลอกจากไดเรกทอรีหนึ่งไปยังอีกไดเรกทอรีหนึ่งคุณสามารถใช้pax:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
... ซึ่งจะใช้การsedแทนที่แบบลักษณะกับชื่อไฟล์ทั้งหมดเมื่อคัดลอก
หากมีเพียงไฟล์และชื่อของพวกเขาไม่มีช่องว่างหรือขึ้นบรรทัดใหม่ (และคุณไม่ได้แก้ไข$IFS) หรือตัวอักษร glob (หรือบางตัวอักษรที่ไม่สามารถพิมพ์ได้ด้วยการใช้งานบางอย่างของls) และไม่เริ่มต้นด้วย.คุณสามารถทำเช่นนี้ :
cp -- $(ls | tail -n 4) ~/
a_หรือไม่ที่จะเติมคำนำหน้าให้กับทั้งสี่ไฟล์อย่างรวดเร็วหรือไม่ ฉันพยายามecho "a_$(ls | tail -n 4)"แต่มันจะเสริมไฟล์แรกเท่านั้น
lsถือว่าเป็นรูปแบบที่ไม่ดีเพราะโดยทั่วไปแล้วจะล้มเหลวอย่างน่ากลัวในชื่อไฟล์ที่ไม่เพียง แต่มีช่องว่าง แต่ยังแท็บบรรทัดใหม่หรือตัวอักษรอื่น ๆ ที่ถูกต้อง แต่ "ยาก"
นี่เป็นวิธีการทุบตีอย่างง่าย ๆ โดยไม่มีรหัสวนซ้ำ คัดลอกไฟล์ 4 ไฟล์ล่าสุดจาก dir ปัจจุบันไปยังปลายทาง:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
findให้ไฟล์ในลำดับที่ต้องการ คุณจะมั่นใจได้อย่างไรว่าไฟล์ที่มีอักขระช่องว่าง (รวมถึงบรรทัดใหม่) ในชื่อถูกจัดการอย่างถูกต้อง?
LC_ALL=C