shellcheck ไม่แนะนำให้ใช้ basename: ทำไม


25

ฉันกำลังพยายามออกshellcheck

ฉันมีบางอย่างเช่นนั้น

basename "${OPENSSL}" 

และฉันได้รับคำแนะนำต่อไปนี้

Use parameter expansion instead, such as ${var##*/}.

จากมุมมองจริงฉันไม่เห็นความแตกต่าง

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

เนื่องจากbasenameเป็นข้อมูลจำเพาะของ POSIXฉันจึงไม่มีเหตุผลว่าทำไมจึงควรเป็นวิธีปฏิบัติที่ดีที่สุด คำใบ้ใด ๆ


8
มันเป็นกระบวนการใหม่เมื่อมันไม่จำเป็นต้อง
จอร์แดน

@ jordanm ยุติธรรมพอ ... ฉันไม่ได้คิดถึงประสิทธิภาพ
Matteo

3
@jordanm ในทางกลับกันมันใช้งานได้กับหอยอื่นที่ไม่ใช่ทุบตี
Matteo

1
@JosephR นั่นคือสิ่งที่ผมคิดว่า cshแต่เพิ่งค้นพบว่ามันไม่ได้ทำงานใน ฉันเดาว่าcshไม่ใช่ POSIX แล้ว
terdon

3
@terdon - csh อยู่ไกลจาก POSIX มาก
จอร์แดน

คำตอบ:


25

มันไม่เกี่ยวกับประสิทธิภาพ - แต่เกี่ยวกับความถูกต้อง basenameใช้การขึ้นบรรทัดใหม่เพื่อกำหนดขอบเขตชื่อไฟล์ที่พิมพ์ออกมา ในกรณีปกติเมื่อคุณส่งชื่อไฟล์เพียงไฟล์เดียวมันจะเพิ่มบรรทัดใหม่ต่อท้ายไปยังเอาต์พุต เนื่องจากชื่อไฟล์อาจมีการขึ้นบรรทัดใหม่ด้วยตนเองจึงทำให้ยากต่อการจัดการชื่อไฟล์เหล่านี้อย่างถูกต้อง

มันซับซ้อนมากขึ้นโดยความจริงที่คนมักจะใช้เช่นนี้basename "$(basename "$file")"สิ่งนี้ทำให้ยิ่งยากมากขึ้นเพราะ$(command)แถบทุกcommandบรรทัดใหม่ต่อท้ายจาก พิจารณากรณีที่ไม่น่า$fileจะจบลงด้วยการขึ้นบรรทัดใหม่ จากนั้นbasenameจะเพิ่มบรรทัดใหม่พิเศษ แต่"$(basename "$file")"จะตัดการขึ้นบรรทัดใหม่ทั้งสองทำให้คุณมีชื่อไฟล์ไม่ถูกต้อง

อีกปัญหาหนึ่งbasenameคือถ้า$fileเริ่มต้นด้วย-(ขีดหรือลบด้วยเครื่องหมายลบ) ก็จะถูกตีความเป็นตัวเลือก อันนี้ง่ายต่อการแก้ไข:$(basename -- "$file")

วิธีที่แข็งแกร่งในการใช้basenameคือ:

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

อีกทางเลือกหนึ่งคือการใช้งาน${file##*/}ซึ่งง่ายกว่า แต่มีข้อบกพร่องของตนเอง โดยเฉพาะอย่างยิ่งมันผิดในกรณีที่$fileเป็นหรือ/foo/


2
คะแนนที่ดีมาก +1 ดีใจที่ OP ยอมรับสิ่งนี้แทนที่จะเป็นของฉัน
terdon

2
ความแตกต่าง: เกิดอะไรขึ้นถ้า$fileเป็นfoo/? เกิดอะไรขึ้นถ้ามัน/?
Gilles 'หยุดความชั่วร้าย'

2
@Gilles: ถูกต้อง ฉันเริ่มคิดว่าbasenameวิธีการนั้นดีกว่ากันเพราะมันแฮ็ค ทางเลือกที่ดีที่สุดที่ฉันสามารถขึ้นมาอยู่${${${file}%%/#}##*/}และ[[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1]ทั้งสองซึ่งเป็น zsh ที่เฉพาะเจาะจงและไม่ที่จัดการ/ได้อย่างถูกต้อง
แมตต์

@terdon ขอบคุณที่คุณไม่ได้ใช้มันเป็นการส่วนตัว :-) ไฟล์ที่มีการขึ้นบรรทัดใหม่ไม่ธรรมดา แต่ Matt มีจุด แน่นอนว่าการใช้การทดแทนตัวแปรนั้นมีประสิทธิภาพมากกว่าเช่นกัน
Matteo

16

เส้นที่เกี่ยวข้องในshellcheck's รหัสที่มาคือ:

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

ไม่มีคำอธิบายที่ชัดเจน แต่ขึ้นอยู่กับชื่อของฟังก์ชั่น ( checkNeedlessCommands) ดูเหมือนว่า @jordanm ค่อนข้างถูกต้องและแนะนำให้คุณหลีกเลี่ยงการตีกระบวนการใหม่


7
แหล่งที่มาอาจจะอยู่กับคุณ :)
โจเซฟอาร์

3

dirname, basename, readlinkฯลฯ (ขอบคุณ @Marco - นี้ได้รับการแก้ไข) สามารถสร้างปัญหาการพกพาเมื่อการรักษาความปลอดภัยเป็นสำคัญ (การรักษาความปลอดภัยที่กำหนดของเส้นทาง) ระบบจำนวนมาก (เช่น Fedora Linux) สถานที่ที่มัน/binขณะที่คนอื่น (เช่น Mac OSX) /usr/binวางไว้ที่ จากนั้นจะมี Bash บน Windows เช่น cygwin, msys และอื่น ๆ มันจะดีกว่าเสมอเมื่ออยู่กับ Bash บริสุทธิ์ถ้าเป็นไปได้ (ต่อความคิดเห็น @Marco)

BTW ขอบคุณสำหรับตัวชี้ไปที่ shellcheck ฉันไม่เคยเห็นมาก่อน


1
1)“ ความปลอดภัย” หมายถึงอะไร? คุณสามารถทำอย่างละเอียด? 2) OP ไม่ได้พูดถึงdirnameเลย 3) ยูทิลิตี้หลักพื้นฐานควรอยู่ใน PATH ไม่ว่าจะเก็บไว้ที่ไหน ฉันยังไม่เห็นระบบที่basenameไม่ได้อยู่ใน PATH 4) สมมติว่ามี bash เป็นปัญหาการพกพา เป็นการดีกว่าเสมอที่จะอยู่ห่างจาก bash และใช้ POSIX เชลล์เมื่อต้องการความสะดวกในการพกพา
Marco

@Marco ขอบคุณสำหรับการชี้ให้เห็นปัญหาเหล่านี้ ใช่คุณถูกต้องว่าสาธารณูปโภคอยู่ในเส้นทาง แต่ที่หนึ่งต้องการให้เพิ่มความปลอดภัยให้กับสคริปต์ Bash มันเป็นวิธีที่ดีในการให้เส้นทางที่แน่นอน ดังนั้นการเรียก '/ bin / basename' จะใช้ได้กับระบบ RedHat แต่มันจะสร้างข้อผิดพลาดบน Mac นี่คือคำอธิบายที่ดีกว่าในBash Cookbookซึ่งประมาณหนึ่งในสี่ของ 600 หน้าจะอุทิศให้กับหัวข้อนี้ เราพยายามอย่างเต็มที่ในการจัดการปัญหาด้านความปลอดภัยที่มีอยู่ในซอร์สฟรีของเราsecure-lib
AsymLabs

@Marco Point 4 เป็นความคิดเห็นที่ถูกต้อง แต่คำถาม (OP) เริ่มต้นด้วยและเขียนรอบshellcheckซึ่งออกแบบมาเพื่อตรวจสอบสคริปต์ sh / bash ทั้งสอง ดังนั้นเราต้องถือว่ามันไม่ได้เป็นอย่างเคร่งครัด Posix โดยค่าเริ่มต้น
AsymLabs

@Marco ความปลอดภัยของตัวแปรสภาพแวดล้อมของเส้นทางสามารถถูกบุกรุกได้อย่างง่ายดาย ตัวอย่างเช่นฉันรู้สึกประหลาดใจมากที่พบว่าเมื่อหลายปีก่อนที่ Ruby RVM ติดตั้งในเครื่องโดยค่าเริ่มต้นวางไว้เป็นพา ธ ก่อนเส้นทางของระบบ
AsymLabs

สหกรณ์กล่าวถึงเฉพาะ POSIX และคำถามที่มีการติดแท็กและไม่ได้posix bashฉันไม่พบตัวบ่งชี้ใด ๆ ที่ OP ต้องการโซลูชันทุบตี คำแถลงของคุณ“ ดีกว่าเสมอที่จะรักษาความบริสุทธิ์ Bash” เป็นเพียงผิดธรรมดาฉันขอโทษ
Marco
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.