ทั้งคู่ต่างก็มีนิสัยแปลก ๆ
ทั้งสองอย่างจำเป็นต้องใช้โดย 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%.}? ฉันอ่านอย่างระมัดระวังและฉันไม่ได้สังเกตว่าคุณพูดถึงข้อเสียใด ๆ