ทำไมการตั้งค่าตัวแปรก่อนที่คำสั่งถูกกฎหมายในทุบตี?


68

ฉันเพิ่งพบคำตอบหลายอย่างเช่นการแยกวิเคราะห์ไฟล์ข้อความที่คั่นด้วย ...ที่ใช้โครงสร้าง:

while IFS=, read xx yy zz;do
    echo $xx $yy $zz
done < input_file

โดยที่IFSตัวแปรถูกตั้งค่าไว้ก่อนreadคำสั่ง

ฉันอ่านการอ้างอิง bash แล้วแต่ไม่สามารถเข้าใจได้ว่าทำไมสิ่งนี้ถึงถูกกฎหมาย

ฉันเหนื่อย

$ x="once upon" y="a time" echo $x $y

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


คำตอบ:


69

มันอยู่ภายใต้เชลล์ไวยากรณ์คำสั่งง่าย ๆ (เน้นการเพิ่ม):

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

ดังนั้นคุณสามารถผ่านตัวแปรใด ๆ ที่คุณต้องการ echoตัวอย่างของคุณไม่ทำงานเนื่องจากตัวแปรถูกส่งผ่านไปยังคำสั่งไม่ได้ตั้งค่าไว้ในเชลล์ เชลล์ขยาย$xและ$y ก่อนที่จะเรียกใช้คำสั่ง งานนี้ตัวอย่างเช่น:

$ x="once upon" y="a time" bash -c 'echo $x $y'
once upon a time

ขอบคุณ! ฉันทำ googling หลายครั้งก่อนถามและพยายามหาว่ามันบอกว่าอย่างไรในการอ้างอิงโดยไม่มีโชค ฉันพยายามทำให้ดีขึ้นในการเขียนสคริปต์ทุบตีและคำตอบของคุณช่วย
Mike Lippert

1
อืมฉันคิดว่าฉันจำเป็นต้องค้นหาข้อมูลอ้างอิงที่ดีกว่าฉัน (ดูลิงค์ด้านบน) ไม่ได้บอกว่ามันไม่มีส่วนของเชลล์ไวยากรณ์
Mike Lippert

1
@MikeLippert ดู 3.7.4 ในการอ้างอิงนั้น ("สภาพแวดล้อมสำหรับคำสั่งหรือฟังก์ชั่นที่เรียบง่ายใด ๆ อาจจะถูกเพิ่มชั่วคราวโดยนำหน้าด้วยการกำหนดพารามิเตอร์") ฉันคิดว่าการอ้างอิงนั้นมาจาก bash รุ่นเก่ากว่า ฉันวิ่งman bashในระบบของฉัน ...
derobert

29

ตัวแปรที่กำหนดกลายเป็นเหมือนตัวแปรสภาพแวดล้อมในกระบวนการทางแยก

ถ้าคุณวิ่ง

A="b" echo $A

จากนั้นทุบตีก่อนขยาย$Aเข้าไป""แล้วเรียกใช้

A="b" echo

นี่คือวิธีที่ถูกต้อง:

x="once upon" y="a time" bash -c 'echo $x $y'

สังเกตเห็นคำพูดเดียวในbash -cมิฉะนั้นคุณจะมีปัญหาเช่นเดียวกับข้างต้น

ดังนั้นตัวอย่างเช่นวงของคุณเป็นกฎหมายเพราะทุบตี builtin 'อ่าน' ,คำสั่งจะมองหาไอเอฟเอในตัวแปรสภาพแวดล้อมของตนและพบว่า ดังนั้น,

for i in `TEST=test bash -c 'echo $TEST'`
do
  echo "TEST is $TEST and I is $i"
done

จะพิมพ์ TEST is and I is test

สุดท้ายสำหรับไวยากรณ์ในห่วงสำหรับสตริงที่คาดว่า ดังนั้นฉันต้องใช้ backticks เพื่อทำให้มันเป็นคำสั่ง IFS=, read xx yy zzอย่างไรก็ตามในขณะที่ลูปคาดว่าไวยากรณ์คำสั่งเช่น


1
ขอบคุณฉันเรียนรู้มากกว่าที่ฉันถามจากคำตอบของคุณ ฉันจะโหวตคำตอบของคุณด้วย แต่ฉันยังไม่ได้รับอนุญาตและฉันตั้งค่าสถานะคำตอบที่ยอมรับในคำถามที่ 1
Mike Lippert

1
ขอบคุณฉันเป็นสิ่งที่ฉันหวังไว้ และการโหวตก็มีความหมายน้อยกว่าการฟังคุณซาบซึ้ง!
Mike Fairhurst

เพื่อชี้แจงความคิดเห็นของคุณภายใต้บรรทัดแรกของรหัส: ใช่กับล้างAทุบตีตัวแปรขยาย$Aเป็นสตริงว่าง แต่เพื่อหลีกเลี่ยงความสับสนผมจะไม่ใช้เพราะรหัสจะไม่เทียบเท่ากับ"" จะมีข้อโต้แย้งที่จะไม่มีA="b" echo "" echo
pabouk

8

man bash

สิ่งแวดล้อม

[... ] สภาพแวดล้อมสำหรับคำสั่งหรือฟังก์ชั่นที่เรียบง่ายใด ๆ อาจจะถูกเพิ่มชั่วคราวโดยนำหน้าด้วยการกำหนดพารามิเตอร์ตามที่อธิบายไว้ข้างต้นในพารามิเตอร์ ข้อความสั่งการมอบหมายเหล่านี้มีผลกระทบต่อสภาพแวดล้อมที่เห็นโดยคำสั่งนั้นเท่านั้น

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

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

ข้อดีของการใช้งานนี้คือคุณสามารถตั้งค่าสภาพแวดล้อมสำหรับกระบวนการย่อยโดยไม่ส่งผลกระทบต่อสภาพแวดล้อมของเชลล์

x="once upon" y="a time" bash -c 'echo $x $y'

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


1
มันบอบบางกว่านั้นเล็กน้อยเพราะตัวอย่างทำงานด้วยx="once upon" y="a time" eval 'echo $x $y'เมื่อไม่มีกระบวนการย่อยที่เกี่ยวข้องเนื่องจากevalเป็น builtin ผมคิดว่าคำพูดที่เกี่ยวข้องจาก manpage The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignmentsคือ พิจารณาตัวอย่างของคำถามที่มันจะต้องมีวิธีนี้ตั้งแต่นี้ยังมีในตัวและจะทำงานร่วมกับรัฐเปลี่ยนแปลงชั่วคราวของread IFS
David Ongaro

4

คำสั่งที่คุณระบุนั้นแตกต่างกันเพราะ$xและ$yถูกขยายก่อนที่echoคำสั่งจะทำงานดังนั้นค่าในเชลล์ปัจจุบันจะถูกใช้ไม่ใช่ค่าที่echoจะเห็นในสภาพแวดล้อมหากมอง


จะขยายอะไรในบรรทัดคำสั่งหลังจากรันคำสั่ง ...
Hauke ​​Laging

การกำหนดคำสั่งล่วงหน้าให้กับxและyมีไว้สำหรับสภาพแวดล้อมที่echoทำงานในไม่ใช่สภาพแวดล้อมที่ข้อโต้แย้งจะechoถูกขยาย สำหรับIFS=, read xx yy zz, สตริงทั้งหมดจะถูกอ่าน, unsplit, โดยreadคำสั่ง แล้วสตริงที่มีการแบ่งตามมูลค่าของIFSที่มีชิ้นส่วนที่เกี่ยวข้องที่ได้รับมอบหมายxx, และyy zz
chepner

ฉันแค่อยากจะชี้ให้เห็นว่าถ้อยคำ "ขยายก่อนที่คำสั่งรัน" จะไม่สมเหตุสมผลเพราะหลังจากการเริ่มต้นคำสั่งไม่มีอะไรที่ถูกขยายอีกต่อไป นอกจากนี้: คุณไม่ได้ดูคำตอบของฉันหรือคุณยังเชื่อว่าฉันต้องการคำอธิบายว่าเกิดอะไรขึ้น ... ?
Hauke ​​Laging

1
ฉันไม่ได้อ้างว่าคุณต้องการคำอธิบายและไม่มีสิ่งใดที่สามารถขยายได้หลังจากคำสั่งรัน แต่ก็เป็นความจริงที่ว่าbashครั้งแรกจะแยกวิเคราะห์บรรทัดคำสั่งให้รับรู้ว่ามีสองการกำหนดตัวแปรที่จะนำไปใช้กับสภาพแวดล้อมของคำสั่งมาที่ระบุคำสั่งในการทำงาน ( echo) ขยายพารามิเตอร์ใด ๆ ที่พบในการขัดแย้งที่แล้วรันคำสั่งechoด้วยข้อโต้แย้งที่ขยาย
chepner

ในกรณีของechoฉันไม่แน่ใจว่ามันจะ "เห็น" ตัวแปรเนื่องจากมันเป็นคำสั่ง builtin และดังนั้นจึงไม่ทำงานใน subshell ซึ่งอาจมีสภาพแวดล้อมของตัวเอง แต่ฉันก็ลองใช้ดูด้วยevalซึ่งมันก็เป็นแบบบิลท์อิน เช่นลองa=xyz eval 'echo $BASHPID $a; grep -z ^a /proc/$BASHPID/{,task/*}/environ'; echo $BASHPID $aแสดงว่าaมีการตั้งค่าเฉพาะภายในevalแม้ว่า pid จะเหมือนกันและสภาพแวดล้อมจะไม่เปลี่ยนแปลงในช่วง Eval! (ในการเข้าถึง/procคุณจำเป็นต้องเรียกใช้สิ่งนี้ภายใต้ Linux) ดูเหมือนว่า bash จะใช้เวทย์มนตร์เพิ่มเติมที่นี่
David Ongaro

2

ฉันจะให้ภาพที่ใหญ่ขึ้นของ " ทำไมมันถูกกฎหมาย"

คำตอบ: เพื่อให้คุณสามารถโทรหรือเรียกใช้โปรแกรมและสำหรับการเรียกใช้เพียงตัวแปรที่มีตัวแปรเฉพาะ

เป็นตัวอย่าง: คุณมีพารามิเตอร์สำหรับการเชื่อมต่อฐานข้อมูลที่เรียกว่า 'db_connection' และโดยปกติคุณจะผ่าน 'test' เป็นชื่อสำหรับการเชื่อมต่อฐานข้อมูลการทดสอบของคุณ ในความเป็นจริงคุณอาจตั้งเป็นค่าเริ่มต้นที่คุณไม่จำเป็นต้องผ่านอย่างชัดเจน บางครั้งคุณต้องการทำงานกับฐานข้อมูล ci ดังนั้นคุณส่งผ่านพารามิเตอร์เป็น 'ci' จากนั้นโปรแกรมที่ถูกเรียกใช้ใช้พารามิเตอร์ฐานข้อมูลนั้นเป็นชื่อของ db เพื่อใช้สำหรับการเรียกฐานข้อมูลทั้งหมด สำหรับการเรียกใช้ครั้งต่อไปหากคุณไม่ทำซ้ำวิธีและเพียงเรียกโปรแกรมตัวแปรจะกลับสู่ค่าเริ่มต้นก่อนหน้า


0

;นอกจากนี้คุณยังสามารถใช้ มันจะถูกประเมินผลก่อนเพราะมันเป็นตัวคั่นคำสั่ง

x="once upon" y="a time"; echo $x $y

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