ทั้งคู่ต่างก็มีนิสัยแปลก ๆ
ทั้งสองอย่างจำเป็นต้องใช้โดย POSIX ดังนั้นความแตกต่างระหว่างทั้งสองจึงไม่ใช่เรื่องของการพกพา¹
วิธีการใช้งานที่เรียบง่ายคือ
base=$(basename -- "$filename")
dir=$(dirname -- "$filename")
สังเกตเครื่องหมายคำพูดคู่รอบ ๆ การแทนที่ตัวแปรเช่นเคยและ--
คำสั่งหลังในกรณีที่ชื่อไฟล์เริ่มต้นด้วยเส้นประ (มิฉะนั้นคำสั่งจะตีความชื่อไฟล์เป็นตัวเลือก) สิ่งนี้ยังคงล้มเหลวในกรณีขอบหนึ่งซึ่งหายาก แต่อาจถูกบังคับโดยผู้ใช้ที่เป็นอันตราย²: การทดแทนคำสั่งลบบรรทัดใหม่ต่อท้าย ดังนั้นถ้าชื่อไฟล์ที่เรียกว่าfoo/bar
แล้วbase
จะถูกตั้งค่าแทนbar
bar
วิธีแก้ปัญหาคือการเพิ่มตัวละครที่ไม่ใช่บรรทัดใหม่และตัดมันหลังจากการทดแทนคำสั่ง:
base=$(basename -- "$filename"; echo .); base=${base%.}
dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
ด้วยการแทนที่พารามิเตอร์คุณจะไม่พบกรณีที่เกี่ยวกับขอบที่เกี่ยวข้องกับการขยายตัวของอักขระแปลก ๆ /
สิ่งหนึ่งที่ไม่ได้เป็นกรณีขอบที่ทุกคนที่ใช้คอมพิวเตอร์เป็นส่วนหนึ่งไดเรกทอรีต้องใช้รหัสที่แตกต่างกันสำหรับกรณีที่ไม่มี
base="${filename##*/}"
case "$filename" in
*/*) dirname="${filename%/*}";;
*) dirname=".";;
esac
เคสขอบคือเมื่อมีเครื่องหมายสแลชต่อท้าย (รวมถึงกรณีของไดเร็กทอรีรูทซึ่งเป็นสแลชทั้งหมด) basename
และdirname
คำสั่งถอดท้ายทับก่อนที่พวกเขาทำผลงานของพวกเขา ไม่มีวิธีที่จะตัดเครื่องหมายทับต่อท้ายในครั้งเดียวถ้าคุณติดกับ POSIX งานสร้าง แต่คุณสามารถทำได้สองขั้นตอน คุณจำเป็นต้องดูแลเคสเมื่ออินพุตประกอบด้วยอะไรนอกจากสแลช
case "$filename" in
*/*[!/]*)
trail=${filename##*[!/]}; filename=${filename%%"$trail"}
base=${filename##*/}
dir=${filename%/*};;
*[!/]*)
trail=${filename##*[!/]}
base=${filename%%"$trail"}
dir=".";;
*) base="/"; dir="/";;
esac
หากคุณรู้ว่าคุณไม่ได้อยู่ในกรณีขอบ (เช่นfind
ผลที่นอกเหนือจากจุดเริ่มต้นมักจะมีส่วนไดเรกทอรีและไม่มีการต่อท้าย/
) แล้วการจัดการสตริงการขยายตัวพารามิเตอร์จะตรงไปตรงมา หากคุณจำเป็นต้องรับมือกับเคสขอบทั้งหมดยูทิลิตี้จะใช้งานง่ายกว่า (แต่ช้ากว่า)
บางครั้งคุณอาจต้องการที่จะรักษาfoo/
เหมือนมากกว่าเช่นfoo/.
foo
หากคุณกำลังทำหน้าที่ในรายการไดเรกทอรีนั้นfoo/
ควรจะเทียบเท่ากับfoo/.
ไม่ใช่foo
; สิ่งนี้สร้างความแตกต่างเมื่อfoo
เป็นลิงก์สัญลักษณ์ไปยังไดเรกทอรี: foo
หมายถึงลิงค์สัญลักษณ์foo/
หมายถึงไดเรกทอรีเป้าหมาย ในกรณีดังกล่าว basename ของเส้นทางที่มีเครื่องหมายทับต่อท้ายจะได้รับการพิจารณาเป็น.
พิเศษและเส้นทางนั้นอาจเป็นชื่อของมันเอง
case "$filename" in
*/) base="."; dir="$filename";;
*/*) base="${filename##*/}"; dir="${filename%"$base"}";;
*) base="$filename"; dir=".";;
esac
วิธีที่รวดเร็วและเชื่อถือได้คือการใช้ zsh กับตัวดัดแปลงประวัติ (แถบแรกจะใช้เครื่องหมายทับต่อท้ายเช่นอรรถประโยชน์):
dir=$filename:h base=$filename:t
less นอกจากว่าคุณกำลังใช้เชลล์ pre-POSIX เช่น Solaris 10 และรุ่นเก่ากว่า/bin/sh
(ซึ่งขาดคุณสมบัติการจัดการสตริงการขยายสตริงบนเครื่องที่ยังคงใช้งานอยู่ - แต่มี POSIX เชลล์ที่เรียกว่าsh
ในการติดตั้ง/usr/xpg4/bin/sh
เสมอไม่ใช่/bin/sh
)
² ตัวอย่าง: ส่งไฟล์ที่เรียกfoo
ไปยังบริการอัปโหลดไฟล์ที่ไม่ได้ป้องกันสิ่งนี้จากนั้นให้ลบและทำให้foo
ถูกลบแทน
base=$(basename -- "$filename"; echo .); base=${base%.}; dir=$(dirname -- "$filename"; echo .); dir=${dir%.}
? ฉันอ่านอย่างระมัดระวังและฉันไม่ได้สังเกตว่าคุณพูดถึงข้อเสียใด ๆ