การอ้างอิงสองครั้งจำเป็นเมื่อใด


120

คำแนะนำเก่าที่ใช้ในการอ้างอิงสองครั้งนิพจน์ใด ๆ ที่เกี่ยวข้องกับ a $VARIABLE, อย่างน้อยถ้าใครอยากให้มันถูกตีความโดยเชลล์เป็นหนึ่งรายการเดียวมิฉะนั้นช่องว่างใด ๆ ในเนื้อหาของ$VARIABLEจะทิ้งเปลือก

อย่างไรก็ตามฉันเข้าใจว่าใน shells รุ่นที่ใหม่กว่านั้นการอ้างสองครั้งไม่จำเป็นอีกต่อไป (อย่างน้อยก็เพื่อจุดประสงค์ที่อธิบายไว้ข้างต้น) ตัวอย่างเช่นในbash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

ในzshทางกลับกันคำสั่งสามคำสั่งเดียวกันก็ประสบความสำเร็จ ดังนั้นตามการทดลองนี้ดูเหมือนว่าในbashหนึ่งสามารถละเว้นอัญประกาศคู่ภายใน[[ ... ]]แต่ไม่ได้อยู่ภายใน[ ... ]หรือในอาร์กิวเมนต์บรรทัดคำสั่งในขณะที่ในzshอัญประกาศคู่อาจถูกละเว้นในทุกกรณีเหล่านี้

แต่การอนุมานกฎทั่วไปจากตัวอย่างเล็ก ๆ น้อย ๆ เช่นที่กล่าวมาเป็นข้อเสนอที่ไม่แน่นอน มันจะเป็นการดีที่ได้เห็นการสรุปว่าเมื่อใดที่การอ้างถึงสองครั้งเป็นสิ่งจำเป็น ฉันสนใจในหลักzsh, และbash/bin/sh


10
พฤติกรรมที่คุณสังเกตเห็นใน zsh ขึ้นอยู่กับการตั้งค่าและได้รับอิทธิพลจากSH_WORD_SPLITตัวเลือก
Ulrich Dangel


3
ชื่อตัวแปร all-caps ถูกใช้โดยตัวแปรที่มีความหมายกับระบบปฏิบัติการและเชลล์ ข้อกำหนด POSIX ให้คำแนะนำอย่างชัดเจนโดยใช้ชื่อตัวพิมพ์เล็กสำหรับตัวแปรที่กำหนดโดยแอ็พพลิเคชัน (ในขณะที่สเปคที่ยกมานั้นเน้นไปที่ตัวแปรสภาพแวดล้อมโดยเฉพาะตัวแปรสภาพแวดล้อมและตัวแปรเชลล์แชร์เนมสเปซ: ความพยายามในการสร้างตัวแปรเชลล์ด้วยชื่อที่ใช้แล้วโดยตัวแปรสภาพแวดล้อมจะเขียนทับหลัง) ดูpubs.opengroup.org/onlinepubs/009695399/basedefs/…ย่อหน้าที่สี่
ชาร์ลส์ดัฟฟี่

คำตอบ:


128

ขั้นแรกให้แยก zsh ออกจากส่วนที่เหลือ มันไม่ได้เป็นเรื่องของเปลือกหอยแบบเก่ากับสมัยใหม่: zsh ทำงานแตกต่างกัน ผู้ออกแบบ zsh ตัดสินใจที่จะไม่เข้ากับเชลล์แบบดั้งเดิม (Bourne, ksh, bash) แต่ใช้งานได้ง่ายกว่า

ประการที่สองมันง่ายกว่ามากที่จะใช้เครื่องหมายคำพูดคู่ตลอดเวลามากกว่าที่จะจำได้เมื่อจำเป็น พวกเขาต้องการเวลาส่วนใหญ่ดังนั้นคุณจะต้องเรียนรู้เมื่อไม่ต้องการไม่ใช่เมื่อจำเป็น

สรุปราคาคู่มีความจำเป็นที่ใดก็ตามที่รายการของคำหรือรูปแบบที่เป็นที่คาดหวัง พวกเขาเป็นทางเลือกในบริบทที่คาดว่าจะเป็นสตริงดิบโดย parser

จะเกิดอะไรขึ้นถ้าไม่มีคำพูด

โปรดทราบว่าหากไม่มีเครื่องหมายคำพูดคู่จะมีสองสิ่งเกิดขึ้น

  1. ก่อนผลลัพธ์ของการขยาย (ค่าของตัวแปรสำหรับการทดแทนพารามิเตอร์เช่น${foo}หรือผลลัพธ์ของคำสั่งสำหรับการทดแทนคำสั่งเช่น$(foo)) จะแบ่งออกเป็นคำที่มันมีช่องว่าง
    แม่นยำยิ่งขึ้นผลลัพธ์ของการขยายตัวจะถูกแยกออกเป็นอักขระแต่ละตัวที่ปรากฏในค่าของIFSตัวแปร (อักขระตัวคั่น) หากลำดับของอักขระตัวคั่นมีช่องว่าง (เว้นวรรคแท็บหรือขึ้นบรรทัดใหม่) ช่องว่างจะถูกนับเป็นอักขระตัวเดียว นำหน้าตัวต่อท้ายหรือตัวคั่นที่ไม่ใช่ช่องว่างซ้ำ ๆ นำไปสู่ฟิลด์ว่าง ตัวอย่างเช่นกับIFS=" :", :one::two : three: :four ผลิตสนามว่างเปล่าก่อนoneระหว่างoneและtwoและ (หนึ่งเดียว) ระหว่างและthreefour
  2. ข้อมูลที่เกิดจากการแยกแต่ละครั้งจะถูกตีความว่าเป็น glob (รูปแบบสัญลักษณ์แทน) \[*?ถ้ามีหนึ่งในตัวละคร หากรูปแบบนั้นตรงกับชื่อไฟล์หนึ่งชื่อขึ้นไปรูปแบบนั้นจะถูกแทนที่ด้วยรายการชื่อไฟล์ที่ตรงกัน

การขยายตัวของตัวแปร unquoted $fooเป็นที่รู้จักเรียกขานว่า“แยก + glob ผู้ประกอบการ” ในทางตรงกันข้ามกับที่ใช้เวลาเพียงค่าของตัวแปร"$foo" fooการทดแทนคำสั่งที่เหมือนกัน"$(foo)"คือการทดแทน$(foo)คำสั่งเป็นการแทนที่คำสั่งตามด้วย split + glob

ที่ที่คุณสามารถละเว้นเครื่องหมายคำพูดคู่

นี่คือทุกกรณีที่ฉันนึกถึงในเชลล์สไตล์ Bourne ซึ่งคุณสามารถเขียนการทดแทนตัวแปรหรือคำสั่งโดยไม่ใส่เครื่องหมายอัญประกาศคู่และค่าจะถูกตีความอย่างแท้จริง

  • ทางด้านขวาของการมอบหมาย

    var=$stuff
    a_single_star=*

    โปรดทราบว่าคุณจำเป็นต้องใส่เครื่องหมายคำพูดคู่หลังจากexportนั้นเพราะมันเป็นบิวด์อินปกติไม่ใช่คำหลัก สิ่งนี้เป็นจริงในเชลล์บางตัวเช่น dash, zsh (เป็น sh emulation), yash หรือ posh; ทุบตีและ ksh ทั้งสองปฏิบัติexportเป็นพิเศษ

    export VAR="$stuff"
  • ในcaseงบ

    case $var in 

    โปรดทราบว่าคุณจำเป็นต้องมีเครื่องหมายคำพูดคู่ในรูปแบบตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ การแยกคำไม่ได้เกิดขึ้นในรูปแบบตัวพิมพ์เล็ก แต่ตัวแปรที่ไม่มีเครื่องหมายถูกตีความว่าเป็นรูปแบบในขณะที่ตัวแปรที่ยกมาถูกตีความว่าเป็นสตริงตัวอักษร

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • ภายในวงเล็บเหลี่ยมสองอัน เครื่องหมายวงเล็บคู่เป็นไวยากรณ์พิเศษของเชลล์

    [[ -e $filename ]]

    ยกเว้นว่าคุณจะต้องมีคำพูดคู่ที่รูปแบบหรือการแสดงออกปกติคาดว่าทางด้านขวามือของ=หรือ==หรือหรือ!==~

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi

    คุณต้องการอัญประกาศคู่ตามปกติภายในวงเล็บปีกกาเดียว[ … ]เนื่องจากเป็นเชลล์ไวยากรณ์ทั่วไป (เป็นคำสั่งที่จะเรียกใช้[) ดูวงเล็บเหลี่ยมเดี่ยวหรือคู่

  • ในการเปลี่ยนเส้นทางในเชลล์ POSIX แบบไม่โต้ตอบ (ไม่ใช่bash, หรือksh88)

    echo "hello world" >$filename

    เชลล์บางตัวเมื่อทำปฏิกิริยาจะถือว่าค่าของตัวแปรเป็นรูปแบบไวด์การ์ด POSIX ห้ามพฤติกรรมดังกล่าวในเชลล์ที่ไม่มีการโต้ตอบ แต่เชลล์บางตัวรวมถึง bash (ยกเว้นในโหมด POSIX) และ ksh88 (รวมถึงเมื่อพบว่าเป็น POSIX shของ Unices เชิงพาณิชย์เช่น Solaris) ที่ยังคงทำอยู่ ( bashเช่นพยายามแยกและการเปลี่ยนเส้นทางล้มเหลวเว้นแต่ว่าการแบ่ง + การแบ่งเป็นวงกลมจะมีคำเดียว) ซึ่งเป็นเหตุผลที่ดีกว่าที่จะอ้างถึงเป้าหมายของการเปลี่ยนเส้นทางในshสคริปต์ในกรณีที่คุณต้องการแปลงเป็นbashสคริปต์ในบางวันหรือเรียกใช้บนระบบที่shเป็น ไม่สอดคล้องกับจุดนั้นหรืออาจมาจากเชลล์แบบโต้ตอบ

  • ภายในนิพจน์ทางคณิตศาสตร์ ในความเป็นจริงคุณจำเป็นต้องเว้นเครื่องหมายอัญประกาศเพื่อให้ตัวแปรแยกวิเคราะห์เป็นนิพจน์ทางคณิตศาสตร์

    expr=2*2
    echo "$(($expr))"

    อย่างไรก็ตามคุณจำเป็นต้องมีเครื่องหมายอัญประกาศล้อมรอบส่วนขยายคณิตศาสตร์เนื่องจากจะต้องมีการแบ่งคำในเชลล์ส่วนใหญ่ตามที่ POSIX ต้องการ (!?)

  • ในตัวห้อยอาร์เรย์ที่เชื่อมโยง

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

ตัวแปรที่ไม่ได้กล่าวถึงและการทดแทนคำสั่งอาจมีประโยชน์ในบางสถานการณ์ที่หายาก:

  • เมื่อค่าตัวแปรหรือเอาต์พุตคำสั่งประกอบด้วยรายการของรูปแบบ glob และคุณต้องการที่จะขยายรูปแบบเหล่านี้ไปยังรายการของไฟล์ที่ตรงกัน
  • เมื่อคุณรู้ว่าค่าไม่มีอักขระตัวแทนใด ๆ นั่น$IFSก็ไม่ได้ถูกแก้ไขและคุณต้องการแยกเป็นอักขระช่องว่าง
  • เมื่อคุณต้องการแยกค่าที่ตัวอักขระบางตัว: ปิดการใช้งานแบบวนรอบด้วยset -fตั้งค่าIFSเป็นตัวคั่น

zsh

ใน zsh คุณสามารถละเว้นเครื่องหมายคำพูดคู่ได้เกือบทุกครั้งโดยมีข้อยกเว้นบางประการ

  • $varไม่เคยขยายไปหลายคำ แต่จะขยายไปยังรายการที่ว่างเปล่า (ตรงข้ามกับรายการที่มีคำเดียวที่ว่างเปล่า) หากค่าของvarคือสตริงที่ว่างเปล่า คมชัด:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    ในทำนองเดียวกัน"${array[@]}"ขยายองค์ประกอบทั้งหมดของอาร์เรย์ในขณะที่$arrayขยายไปยังองค์ประกอบที่ไม่ว่างเปล่าเท่านั้น

  • ธงขยายตัวพารามิเตอร์บางครั้งต้องใช้คำพูดสองรอบการเปลี่ยนตัวผู้เล่นทั้งหมด:@"${(@)foo}"

  • การทดแทนคำสั่งผ่านการแยกฟิลด์หากไม่ได้อ้างอิง: echo $(echo 'a'; echo '*')พิมพ์a *(ด้วยช่องว่างเดียว) ในขณะที่echo "$(echo 'a'; echo '*')"พิมพ์สตริงสองบรรทัดที่ไม่ได้แก้ไข ใช้"$(somecommand)"เพื่อให้ได้ผลลัพธ์ของคำสั่งในคำเดียวบรรทัดใหม่สุดท้าย ใช้"${$(somecommand; echo _)%?}"เพื่อรับเอาต์พุตที่แน่นอนของคำสั่งรวมถึงการขึ้นบรรทัดใหม่ ใช้"${(@f)$(somecommand)}"เพื่อรับอาร์เรย์ของบรรทัดจากเอาต์พุตของคำสั่ง


ในความเป็นจริงคุณจำเป็นต้องเว้นเครื่องหมายอัญประกาศเพื่อให้ตัวแปรแยกวิเคราะห์เป็นนิพจน์ทางคณิตศาสตร์ เหตุใดฉันจึงสามารถทำให้ตัวอย่างของคุณทำงานกับอัญประกาศ:echo "$(("$expr"))"
Cyker

นี่คือสิ่งที่man bashพูดว่า: การแสดงออกจะได้รับการปฏิบัติราวกับว่ามันอยู่ในเครื่องหมายคำพูดคู่ แต่เครื่องหมายคำพูดคู่ภายในวงเล็บไม่ได้รับการปฏิบัติเป็นพิเศษ
Cyker

4
นอกจากนี้สำหรับผู้ที่สนใจที่ชื่ออย่างเป็นทางการของการแยก + globมีการแยกคำและการขยายตัวของพา
Cyker

3
FYI - มากกว่าใน StackOverflowผมเคยมีใครดึง "ตัวเลือกเมื่อสตริงดิบคาดว่า" echoภาษาในคำตอบนี้ออกมาเพื่อปกป้องไม่อ้างโต้แย้งไปยัง มันอาจจะคุ้มค่าที่จะลองใช้ภาษาให้ชัดเจนยิ่งขึ้น ("เมื่อตัวแยกวิเคราะห์" คาดว่าจะเป็นสตริงดิบ)
Charles Duffy

2
@CharlesDuffy Ugh ฉันไม่เคยคิดที่จะเข้าใจผิด ฉันเปลี่ยน“ ตำแหน่ง” เป็น“ เมื่อ” และเพิ่มประโยคตามที่คุณแนะนำ
Gilles
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.