bash: ตัวแปรสูญเสียค่าเมื่อสิ้นสุดการอ่านลูป


36

ฉันมีปัญหากับหนึ่งในสคริปต์เชลล์ของฉัน ถามเพื่อนร่วมงานไม่กี่คน แต่พวกเขาทั้งหมดเพิ่งส่ายหัว (หลังจากมีรอยขีดข่วน) ดังนั้นฉันจึงมาที่นี่เพื่อหาคำตอบ

ตามความเข้าใจของฉันเชลล์สคริปต์ต่อไปนี้ควรพิมพ์ "Count is 5" เป็นบรรทัดสุดท้าย ยกเว้นว่ามันจะไม่ มันพิมพ์ "นับเป็น 0" หาก "ขณะที่อ่าน" ถูกแทนที่ด้วยลูปชนิดอื่นมันก็ใช้ได้ดี นี่คือสคริปต์:

echo "1"> input.data
echo "2" >> input.data
echo "3" >> input.data
echo "4" >> input.data
echo "5" >> input.data

CNT = 0 

cat input.data | ในขณะที่อ่าน
ทำ
  ให้ CNT ++;
  echo "นับเป็น $ CNT"
เสร็จแล้ว 
echo "นับเป็น $ CNT"

เหตุใดสิ่งนี้จึงเกิดขึ้นและฉันจะป้องกันได้อย่างไร ฉันได้ลองสิ่งนี้ใน Debian Lenny และ Squeeze แล้วผลลัพธ์เดียวกัน (เช่น bash 3.2.39 และ bash 4.1.5 ฉันยอมรับอย่างเต็มที่ว่าไม่ได้เป็นตัวช่วยสร้างสคริปต์ของเชลล์

คำตอบ:


30

ดูอาร์กิวเมนต์ @ รายการคำถามที่พบบ่อย Bash # 24: "ฉันตั้งค่าตัวแปรในลูปทำไมพวกเขาจึงหายไปหลังจากลูปยุติหรือทำไมฉันไม่สามารถอ่านข้อมูลได้?" (เก็บถาวรที่นี่ล่าสุด )

สรุป: สิ่งนี้ได้รับการสนับสนุนจาก bash 4.2 ขึ้นไปเท่านั้น คุณต้องใช้วิธีที่แตกต่างเช่นการแทนที่คำสั่งแทนไพพ์หากคุณใช้ bash


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

5
ลิงก์ตาย นี่คือเหตุผลที่คำตอบสำหรับลิงค์เท่านั้นไม่ดี อย่างน้อยก็สรุปคำตอบที่นี่
rudolfbyker

พระเจ้าอีกครั้งที่ ksh ดีกว่ามาก ... ทำไมแค่ทำไมทุกคนแห่กันไปทุบตี
Florian Heigl

@ FlorianHeigl: คุณอ้างว่า ksh คือ One True Shell หรือไม่
Ignacio Vazquez-Abrams

@ IgnacioVazquez-Abrams no แต่ฉันอ้างว่าขณะที่การวนลูปในการทุบตีเป็น PITA ที่น่ากลัว การจัดการแบบวนซ้ำนั้นเป็นทางยาวที่ทำให้ไม่สามารถใช้งานได้ในปี 1993 สิ่งอื่น ๆ คือการจัดการที่ getopt ซึ่งตัวจัดการบิวอิน (เช่น 1993) นั้นเรียบง่ายและมีความสามารถบางอย่างที่คุณยังไม่สามารถรับได้เว้นแต่จะใช้ docopt ฉันอ้างว่าทุบตีทำให้ตัวเองโค้งกว่า 20 ปียืนกรานและใช้เวลากับสิ่งนี้ที่นี่หรือการใช้ของคนเลวนับล้านใช้เกินกว่าที่จะยอมรับได้เพราะคนส่วนใหญ่ไม่เคยรู้
Florian Heigl

30

นี่เป็นความผิดพลาด 'ทั่วไป' ไพพ์สร้าง SubShells ดังนั้นจึงwhile readรันบนเชลล์ที่แตกต่างจากสคริปต์ของคุณซึ่งทำให้CNTตัวแปรของคุณไม่มีวันเปลี่ยนแปลง (เพียงอันเดียวในเชลล์ย่อยของไพพ์)

จัดกลุ่มกลุ่มสุดท้ายechoด้วย subshell whileเพื่อแก้ไข (มีอีกหลายวิธีในการแก้ไขนี่คือคำตอบ Iain และ Ignacio มีคนอื่น ๆ )

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

คำอธิบายยาว:

  1. คุณประกาศCNTในสคริปต์ของคุณจะเป็นค่า 0;
  2. SubShell เริ่มต้นในการ|ถึงwhile read;
  3. $CNTตัวแปรของคุณถูกส่งออกไปยัง SubShell ด้วยค่า 0;
  4. SubShell จะนับและเพิ่มCNTมูลค่าเป็น 5
  5. SubShell สิ้นสุดลงตัวแปรและค่าจะถูกทำลาย (ไม่กลับไปที่กระบวนการ / สคริปต์ที่เรียก)
  6. คุณechoมีCNTค่าเริ่มต้นเป็น0

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

คำตอบที่ยอดเยี่ยมน่าเสียดายที่ฉันสามารถให้โบนัสการตอบรับเดียวเท่านั้น ขอโทษ
wolfgangsz

10

วิธีนี้ใช้ได้ผล

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

ฉันชอบที่วิธีที่ชาญฉลาดเพราะคุณรู้ว่าข้อมูลที่ต้องการอยู่ที่ไหนและจะต้องได้รับมันกลับมา หากคุณไม่รู้วิธีแก้ปัญหาที่มีทักษะสูงคุณสามารถ "อ่านไฟล์" hahahha ได้ตลอดเวลา +1 สำหรับคุณ
erm3nda

1
ทุกคนที่อ่านสิ่งนี้โปรดทราบว่าโซลูชันที่ Iain จัดทำขึ้นเฉพาะเมื่อคุณมีสคริปต์ที่เรียกใช้ bash อย่างชัดเจนโดยมีบรรทัดแรก: #! / bin / bash และ: #! / bin / sh จะไม่ทำงาน
Roadowl

1
ที่น่าสนใจตัวอย่างแรกที่ผมเคยเห็นที่ใช้ประโยชน์ของแมวจริงการป้องกันรหัสจากการทำงาน โดยวิธีการ @Roadowl เพียง bashism ที่นี่คือบรรทัดlet CNT++ที่ควรจะCNT="$((CNT+1))"ใช้ประโยชน์จากการขยายตัวทางคณิตศาสตร์ที่สอดคล้องกับ POSIXแทน ที่เหลือก็พกพาไปเรียบร้อยแล้ว
สัญลักษณ์แทน

6

ลองส่งข้อมูลใน sub-shell แทนเช่นมันเป็นไฟล์ก่อนวงวน สิ่งนี้คล้ายกับโซลูชันของ lain แต่สมมติว่าคุณไม่ต้องการไฟล์บางไฟล์เป็นระยะ ๆ :

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.