ฉันมีไฟล์หลายไฟล์ที่เรียงลำดับโดยชื่อไฟล์ในไดเรกทอรี ฉันต้องการคัดลอกไฟล์ 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]) ~
เพิ่มD
qualifier หากคุณต้องการพิจารณาไฟล์ที่ซ่อนอยู่ (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