เหตุใดตัวแปรภายในของฉันจึงอยู่ใน 'ขณะที่อ่าน' แต่ไม่ใช่ในวงอื่นที่คล้ายกัน


25

เหตุใดฉันจึงได้รับค่าต่าง ๆ$xจากตัวอย่างด้านล่าง

#!/bin/bash

x=1
echo fred > junk ; while read var ; do x=55 ; done < junk
echo x=$x 
#    x=55 .. I'd expect this result

x=1
cat junk | while read var ; do x=55 ; done
echo x=$x 
#    x=1 .. but why?

x=1
echo fred | while read var ; do x=55 ; done
echo x=$x 
#    x=1  .. but why?

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

คำตอบ:


26

คำอธิบายที่ถูกต้องได้รับจากjsbillingsและgeekosaurแล้ว แต่ขอผมขยายอีกหน่อย

ในเชลล์ส่วนใหญ่รวมถึง bash แต่ละไพพ์ไลน์ทำงานในเชลล์ย่อยดังนั้นการเปลี่ยนแปลงใด ๆ ในสถานะภายในของเชลล์ (เช่นตัวแปรการตั้งค่า) จะยังคงอยู่ในเซ็กเมนต์ของไพพ์ไลน์ ข้อมูลเดียวที่คุณจะได้รับจาก subshell คือสิ่งที่มันส่งออก (ไปยังเอาต์พุตมาตรฐานและตัวอธิบายไฟล์อื่น ๆ ) และรหัสทางออก (ซึ่งเป็นตัวเลขระหว่าง 0 ถึง 255) ตัวอย่างเช่นตัวอย่างต่อไปนี้พิมพ์ 0:

a=0; a=1 | a=2; echo $a

ใน ksh (ตัวแปรที่ได้มาจากรหัส AT&T ไม่ใช่ตัวแปร pdksh / mksh) และ zsh รายการสุดท้ายในไพพ์ไลน์จะถูกดำเนินการในพาเรนต์เชลล์ (POSIX อนุญาตให้ทำงานทั้งสองอย่าง) ดังนั้นข้อมูลโค้ดด้านบนจะพิมพ์ 2

สำนวนที่มีประโยชน์คือการรวมความต่อเนื่องของ while loop (หรืออะไรก็ตามที่คุณมีอยู่ทางด้านขวามือของไปป์ไลน์ แต่ a while loop เป็นเรื่องธรรมดาจริงๆที่นี่) ในไพพ์ไลน์:

cat junk | {
  while read var ; do x=55 ; done
  echo x=$x 
}

1
ขอบคุณ Gilles .. นั่นคือ = 0; a = 1 | a = 2 ให้ภาพที่ชัดเจนมาก .. และไม่เพียง แต่การแปลของสถานะภายใน แต่ยังรวมถึงไปป์ไลน์ที่จริงไม่จำเป็นต้องส่งอะไรผ่านไพพ์ (นอกเหนือจากรหัสทางออก (?) .. ในตัวของมันเอง เป็นข้อมูลเชิงลึกที่น่าสนใจเกี่ยวกับไปป์ ... ฉันจัดการเพื่อให้สคริปต์ทำงานด้วย< <(locate -ber ^\.tag$)ขอบคุณคำตอบที่ไม่ชัดเจนเล็กน้อยต้นฉบับและ geekosaur และ glenn jackman comemnts .. ตอนแรกฉันตกอยู่ในสถานการณ์ที่ต้องยอมรับคำตอบ แต่ผลสุทธินั้น ก็สวยใสโดยเฉพาะอย่างยิ่งกับ jsbillings ติดตามความคิดเห็น :)
Peter.O

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

8

คุณกำลังประสบปัญหาขอบเขตตัวแปร ตัวแปรที่กำหนดในห่วงลูปที่อยู่ทางด้านขวาของไพพ์มีบริบทขอบเขตของตนเองและการเปลี่ยนแปลงตัวแปรจะไม่ถูกมองเห็นภายนอกลูป ขณะที่ loop เป็น subshell ที่ได้รับCOPYของสภาพแวดล้อมของเชลล์และการเปลี่ยนแปลงใด ๆ ในสภาพแวดล้อมจะหายไปในตอนท้ายของเชลล์ ดูคำถาม StackOverflowนี้

อัปเดต : ฉันละเลยที่จะชี้ให้เห็นความจริงที่สำคัญว่าในขณะที่ลูปที่มี subshell ของตัวเองนั้นเป็นเพราะเป็นจุดสิ้นสุดของไพพ์ฉันได้อัปเดตคำตอบนั้นแล้ว


@jsbillings .. โอเคที่อธิบายสองตัวอย่างสุดท้าย แต่มันไม่ได้อธิบายครั้งแรกโดยที่ค่าของ $ x ที่กำหนดไว้ในลูปจะดำเนินการเป็น55 (เกินขอบเขตของ 'ห่วง' ในขณะที่)
Peter.O

5
@ fred.bear: มันกำลังwhileวนลูปเป็นปลายหางของไปป์ไลน์ที่โยนมันลงใน subshell
geekosaur

2
นี่คือที่มาของการทดแทนกระบวนการทุบตี แทนที่จะเป็นblah|blah|while read ...คุณสามารถมีได้while read ...; done < <(blah|blah)
เกล็นแจ็คแมน

1
@geekosaur: ขอบคุณสำหรับการกรอกรายละเอียดที่ฉันไม่ได้ใส่ไว้ในคำตอบ
jsbillings

1
-1 ขออภัย แต่คำตอบนี้ผิด มันอธิบายวิธีการทำงานของสิ่งนี้ในภาษาการเขียนโปรแกรมมากมาย แต่ไม่ใช่ในเชลล์ @Gilles ด้านล่างทำให้ถูกต้อง
jpc

6

ในฐานะที่เป็นที่กล่าวถึงในคำตอบอื่น ๆชิ้นส่วนของท่อที่ทำงานใน subshells ดังนั้นการปรับเปลี่ยนที่ทำมีไม่มองเห็นได้ด้วยเปลือกหลัก

หากเราพิจารณาเพียงแค่ Bash มีวิธีแก้ไขปัญหาอื่นอีกสองประการนอกเหนือจากcmd | { stuff; more stuff; }โครงสร้าง:

  1. เปลี่ยนเส้นทางอินพุตจากการทดแทนกระบวนการ :

    while read var ; do x=55 ; done < <(echo fred)
    echo "$x"

    เอาต์พุตจากคำสั่ง in <(...)ถูกสร้างขึ้นเพื่อให้ดูเหมือนว่าเป็นไพพ์ที่มีชื่อ

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

    bash -c '
      shopt -s lastpipe
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

    หรือ

    bash -O lastpipe -c '
      echo fred | while read var ; do x=55 ; done; 
      echo "$x"
    '

การทดแทนกระบวนการนั้นรองรับ ksh และ zsh ด้วยเช่นกัน แต่เนื่องจากพวกเขาเรียกใช้ส่วนสุดท้ายของไปป์ไลน์ในเชลล์หลักอย่างไรก็ตามการใช้เป็นวิธีแก้ไขปัญหาจึงไม่จำเป็นจริงๆ


0
#!/bin/bash
set -x

# prepare test data.
mkdir -p ~/test_var_global
cd ~/test_var_global
echo "a"> core.1
echo "b"> core.2
echo "c"> core.3


var=0

coreFiles=$(find . -type f -name "core*")
while read -r file;
do
  # perform computations on $i
  ((var++))
done <<EOF
$coreFiles
EOF

echo $var

Result:
...
+ echo 3
3

มันสามารถทำงานได้

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