ทำไมฉันต้องอ้างตัวแปรสำหรับถ้า แต่ไม่ใช่เพื่อ echo


26

ฉันได้อ่านแล้วว่าคุณต้องการอัญประกาศคู่เพื่อขยายตัวแปรเช่น

if [ -n "$test" ]; then echo '$test ok'; else echo '$test null'; fi

จะทำงานตามที่คาดไว้ในขณะที่

if [ -n $test ]; then echo '$test ok'; else echo '$test null'; fi

จะพูดเสมอ$test okแม้ว่า$testจะเป็นโมฆะ

แต่ทำไมเราไม่ต้องการคำพูดecho $test?


2
หากคุณไม่ได้อ้างตัวแปรเพื่อใช้เป็นอาร์กิวเมนต์ให้echoเว้นวรรคและการขึ้นบรรทัดใหม่จะถูกปล้น
jordanm

คำตอบ:


36

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

บริบทรายการรวมถึงการขัดแย้งกับคำสั่งง่าย ๆ เช่น[หรือecho, การfor i in <here>กำหนดให้กับอาร์เรย์ ... มีบริบทอื่น ๆ ที่ตัวแปรยังจำเป็นต้องยกมา ที่ดีที่สุดคือการอ้างอิงตัวแปรเสมอถ้าคุณไม่มีเหตุผลที่ดี

คิดว่ากรณีที่ไม่มีคำพูด (ในบริบทรายการ) ในขณะที่แยก + globผู้ประกอบการ

ราวกับว่าเป็นecho $testecho glob(split("$test"))

พฤติกรรมของเชลล์ทำให้คนส่วนใหญ่สับสนเพราะในภาษาอื่น ๆ ส่วนใหญ่คุณใส่เครื่องหมายคำพูดรอบสตริงคงที่เช่นputs("foo")และไม่ใช่ตัวแปร (เช่นputs(var)) ในขณะที่เชลล์เป็นอีกวิธีหนึ่ง: ทุกอย่างเป็นสตริงในเชลล์ดังนั้นการใส่เครื่องหมายคำพูดรอบ ๆ ทุกสิ่ง จะยุ่งยากคุณคุณไม่จำเป็นต้องecho test "echo" "test"ในเปลือกคำพูดจะใช้สำหรับสิ่งอื่น: ป้องกันความหมายพิเศษบางอย่างของตัวละครบางตัวและ / หรือส่งผลกระทบต่อพฤติกรรมของการขยายตัวบางอย่าง

ใน[ -n $test ]หรือecho $testเชลล์จะแบ่ง$test(ตามค่าเริ่มต้น) แล้วดำเนินการสร้างชื่อไฟล์ (ขยาย*รูปแบบทั้งหมด'?' ... ไปยังรายการไฟล์ที่ตรงกัน) แล้วส่งรายการอาร์กิวเมนต์ไปยัง[หรือechoคำสั่ง .

"[" "-n" glob(split("$test")) "]"อีกครั้งคิดว่ามันเป็น ถ้า$testว่างเปล่าหรือมีเพียงช่องว่าง (spc, tab, nl) ดังนั้นโอเปอเรเตอร์แยก + glob จะส่งคืนรายการว่างดังนั้นค่า[ -n $test ]จะเป็น"[" "-n" "]"ซึ่งเป็นการทดสอบเพื่อตรวจสอบว่า "-n" เป็นสตริงว่างหรือไม่ แต่ลองจินตนาการว่าจะเกิดอะไรขึ้นถ้า$testเป็น "*" หรือ "= foo" ...

ใน[ -n "$test" ], [ถูกส่งผ่านไปสี่ข้อโต้แย้ง"[", "-n", ""และ"]"(ไม่ทราบราคา) ซึ่งเป็นสิ่งที่เราต้องการ

ไม่ว่าจะเป็นechoหรือ[สร้างความแตกต่างไม่เพียง แต่มันจะechoส่งออกสิ่งเดียวกันไม่ว่าจะผ่านการโต้แย้งว่างเปล่าหรือไม่มีการโต้แย้งเลย

ดูเพิ่มเติมที่คำตอบสำหรับคำถามที่คล้ายกันนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ[คำสั่งและ[[...]]โครงสร้าง


7

@ คำตอบของ h3rrmillerดีสำหรับการอธิบายว่าทำไมคุณต้องมีเครื่องหมายคำพูดสำหรับif(หรือมากกว่า, [/ test) แต่จริง ๆ แล้วฉันจะตอบว่าคำถามของคุณไม่ถูกต้อง

ลองคำสั่งต่อไปนี้แล้วคุณจะเห็นว่าฉันหมายถึงอะไร

export testvar="123    456"
echo $testvar
echo "$testvar"

การแทนที่ตัวแปรจะทำให้คำสั่งที่สองขยายไปถึง:

echo 123    456

และช่องว่างหลายรายการถูกยุบลงหนึ่งช่อง

echo 123 456

ด้วยเครื่องหมายคำพูดช่องว่างจะถูกเก็บไว้

นี้เกิดขึ้นเพราะเมื่อคุณพูดพารามิเตอร์ (ไม่ว่าพารามิเตอร์ที่ส่งผ่านไปecho, testหรือคำสั่งอื่น ๆ ) ค่าของพารามิเตอร์ที่จะถูกส่งเป็นหนึ่งมูลค่าให้กับคำสั่ง หากคุณไม่ได้อ้างถึงมันเชลล์จะใช้เวทย์มนตร์ปกติในการค้นหาช่องว่างเพื่อกำหนดว่าแต่ละพารามิเตอร์เริ่มต้นและสิ้นสุดที่ใด

สิ่งนี้สามารถแสดงได้โดยโปรแกรม C ต่อไปนี้ (ง่ายมาก) ลองต่อไปนี้ในบรรทัดคำสั่ง (คุณอาจต้องการทำในไดเรกทอรีว่างเปล่าเพื่อไม่ให้เสี่ยงต่อการเขียนทับบางสิ่ง)

cat <<EOF >paramtest.c
#include <stdio.h>
int main(int argc, char **argv) {
  int nparams = argc-1; /* because 1 parameter means only the executable's name */
  printf("%d parameters received\n", nparams);
  return nparams;
}
EOF
cc -o paramtest paramtest.c

แล้ว ...

./paramtest 123 456
./paramtest "123 456"
./paramtest 123   456
./paramtest "123   456"

หลังจากที่ทำงานparamtest, $?จะถือจำนวนพารามิเตอร์มันก็ผ่านไป (และตัวเลขที่จะถูกพิมพ์)


2

นี่คือทั้งหมดที่เกี่ยวกับวิธีที่เชลล์ตีความบรรทัดก่อนที่โปรแกรมจะทำงาน

หากบรรทัดอ่านecho I am $USERเชลล์จะขยายออกเป็นecho I am blrflและechoไม่มีเงื่อนงำว่าที่มาของข้อความเป็นตัวอักษรหรือการขยายตัวแปร ในทำนองเดียวกันหากมีการอ่านบรรทัดecho I am $UNDEFINEDเชลล์จะขยาย$UNDEFINEDไปเป็นอะไรและข้อโต้แย้งของ echo จะเป็นI amและนั่นคือจุดสิ้นสุดของมัน เนื่องจากechoใช้ได้ดีโดยไม่มีข้อโต้แย้งecho $UNDEFINEDจึงใช้ได้อย่างสมบูรณ์

ปัญหาของคุณifไม่ได้เกิดifขึ้นเพราะifเพียงแค่เรียกใช้โปรแกรมและข้อโต้แย้งใดก็ตามก็ตามและดำเนินการthenส่วนหนึ่งหากโปรแกรมออก0(หรือelseส่วนที่หากมีอยู่และโปรแกรมออกจากที่ไม่ใช่0):

if /bin/true ; then echo True dat. ; fi
if fgrep -q blrfl /etc/passwd ; then echo Blrfl has an account. ; fi

เมื่อคุณใช้if [ ... ]ทำการเปรียบเทียบคุณไม่ได้ใช้ primitives ที่มีอยู่ในเชลล์ คุณจริงสอนเปลือกเพื่อเรียกใช้โปรแกรมที่เรียกว่า[ซึ่งเป็นซูเปอร์เล็กน้อยมากของที่ต้องใช้อาร์กิวเมนต์สุดท้ายtest(1) ]โปรแกรมทั้งสองออกจาก0ถ้าเงื่อนไขการทดสอบออกมาเป็นความจริงและ1ถ้ามันไม่ได้

สาเหตุที่การทดสอบบางอย่างผิดพลาดเมื่อไม่ได้กำหนดตัวแปรเนื่องจากtestไม่เห็นว่าคุณกำลังใช้ตัวแปรอยู่ ดังนั้น[ $UNDEFINED -eq 2 ]หยุดเพราะเวลาที่เปลือกทำกับมันทุกคนtestเห็นว่ามีข้อโต้แย้ง-eq 2 ]ซึ่งไม่ใช่การทดสอบที่ถูกต้อง หากคุณทำสิ่งที่กำหนดไว้เช่น[ $DEFINED -ne 0 ]จะทำงานได้เพราะเชลล์จะขยายให้เป็นการทดสอบที่ถูกต้อง (เช่น0 -ne 0)

มีความแตกต่างทางความหมายระหว่างfoo $UNDEFINED barซึ่งขยายออกเป็นสองข้อโต้แย้ง ( fooและbar) เพราะ$UNDEFINEDอยู่กับชื่อของมัน เปรียบเทียบสิ่งนี้กับfoo "$UNDEFINED" barซึ่งขยายเป็นสามอาร์กิวเมนต์ ( foo, สตริงว่างและ `บาร์) คำพูดบังคับให้เชลล์ตีความว่าพวกเขาเป็นข้อโต้แย้งว่ามีอะไรเกิดขึ้นระหว่างพวกเขาหรือไม่


0

ไม่ทราบราคา$testสามารถขยายจะเป็นมากกว่าคำเดียวดังนั้นจึงต้องการที่จะยกมาเพื่อที่จะไม่ทำลายไวยากรณ์เนื่องจากแต่ละสวิทช์ภายใน[คำสั่งคาดว่าอาร์กิวเมนต์หนึ่งซึ่งเป็นสิ่งที่คำพูดทำ (ทำให้สิ่งที่$testจะขยายเป็นหนึ่งอาร์กิวเมนต์)

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

ดูที่Bash Pitfalls


ใช่ แต่ทำไมเราไม่ต้องการมันecho?
CharlesB

@CharlesB echoคุณไม่จำเป็นต้องใช้คำพูดเพื่อ อะไรที่ทำให้คุณคิดอย่างอื่น
Gilles 'ดังนั้นหยุดความชั่วร้าย'

ฉันไม่ต้องการพวกเขาที่ฉันสามารถecho $testและการทำงาน (มันจะออกผลลัพธ์ค่าของ $ ทดสอบ)
CharlesB

1
@CharlesB มันจะส่งออกเฉพาะมูลค่าของการทดสอบ $ หากที่ไม่ได้มีหลายช่องว่างที่ใดก็ได้ ลองใช้โปรแกรมในคำตอบของฉันเพื่อแสดงเหตุผล
CVN

0

พารามิเตอร์ที่ว่างเปล่าจะถูกลบหากไม่ได้อ้างอิง:

start cmd:> strace -e trace=execve echo foo $bar baz
execve("/usr/bin/echo", ["echo", "foo", "baz"], [/* 100 vars */]) = 0

start cmd:> strace -e trace=execve echo foo "$bar" baz
execve("/usr/bin/echo", ["echo", "foo", "", "baz"], [/* 100 vars */]) = 0

คำสั่งที่เรียกว่าไม่เห็นว่ามีพารามิเตอร์ว่างในบรรทัดคำสั่งเชลล์ ดูเหมือนว่า [ถูกกำหนดให้ส่งคืน 0 สำหรับ -n โดยไม่มีอะไรติดตาม Whyever

การอ้างอิงจะสร้างความแตกต่างสำหรับ echo ด้วยในหลายกรณี:

var='*'
echo $var
echo "$var"

var="foo        bar"
echo $var
echo "$var"

2
ไม่ใช่echoมันเป็นเปลือก lsคุณจะเห็นพฤติกรรมเดียวกันกับ ลองtouch '*'สักครั้งถ้าคุณรู้สึกว่าการผจญภัย :)
CVn

นั่นเป็นเพียงถ้อยคำเนื่องจากไม่มีความแตกต่างกับกรณี 'ถ้า [... ]' [ไม่ใช่คำสั่งเชลล์พิเศษ มันแตกต่างจาก [[(ในทุบตี) ที่ไม่จำเป็นต้องมีการอ้างอิง
Hauke ​​Laging
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.