ทำความเข้าใจกับการทดแทนคำสั่ง Read-a-File ของ Bash


11

ฉันพยายามเข้าใจว่า Bash ปฏิบัติต่อบรรทัดต่อไปนี้อย่างไร:

$(< "$FILE")

ตามหน้า Bash man นี่เทียบเท่ากับ:

$(cat "$FILE")

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

อย่างไรก็ตามสำหรับบรรทัดแรกที่ฉันกล่าวถึงข้างต้นฉันเข้าใจว่า: Bash ทำการแทนที่ตัวแปรใน$FILEBash เปิด$FILEสำหรับการอ่านในอินพุตมาตรฐานอินพุตมาตรฐานจะถูกคัดลอกไปยังเอาต์พุตมาตรฐานการทดแทนคำสั่งเสร็จสิ้นและ Bash พยายามเรียกใช้มาตรฐานที่เป็นผลลัพธ์ เอาท์พุต

ใครช่วยอธิบายให้ฉันฟังได้ว่าเนื้อหาของการเปลี่ยน$FILEจาก stdin เป็น stdout ได้อย่างไร?

คำตอบ:


-3

<ไม่ได้โดยตรงลักษณะของทุบตีแทนคำสั่ง มันเป็นโอเปอเรเตอร์การเปลี่ยนเส้นทาง (เช่นไพพ์) ซึ่งบางเชลล์อนุญาตโดยไม่มีคำสั่ง (POSIX ไม่ได้ระบุลักษณะการทำงานนี้)

บางทีอาจจะมีความชัดเจนมากขึ้นด้วยช่องว่างเพิ่มเติม:

echo $( < $FILE )

สิ่งนี้มีประสิทธิภาพ * เหมือนกับ POSIX ที่ปลอดภัยยิ่งขึ้น

echo $( cat $FILE )

... ซึ่งยังมีประสิทธิภาพ *

echo $( cat < $FILE )

มาเริ่มกันที่เวอร์ชั่นสุดท้ายกัน สิ่งนี้จะทำงานcatโดยไม่มีการขัดแย้งซึ่งหมายความว่ามันจะอ่านจากอินพุตมาตรฐาน $FILEถูกเปลี่ยนเส้นทางไปยังอินพุตมาตรฐานเนื่องจาก<ดังนั้นcatใส่เนื้อหาลงในเอาต์พุตมาตรฐาน $(command)Subsitution แล้วผลักดันของการส่งออกเข้าสู่ข้อโต้แย้งcatecho

ในbash(แต่ไม่ใช่ในมาตรฐาน POSIX) คุณสามารถใช้<โดยไม่มีคำสั่ง bash(และzshและkshแต่ไม่dash) จะตีความว่าราวกับว่าcat <โดยไม่ต้องเรียกใช้กระบวนการย่อยใหม่ เนื่องจากนี่เป็นแบบดั้งเดิมของเชลล์จึงเร็วกว่าการรันคำสั่งภายนอกcatอย่างแท้จริง *นี่คือเหตุผลที่ฉันพูดว่า "มีประสิทธิภาพเหมือนกับ"


ดังนั้นในย่อหน้าสุดท้ายเมื่อคุณพูดว่า " bashจะตีความว่าเป็นcat filename" คุณหมายถึงพฤติกรรมนี้เฉพาะกับการทดแทนคำสั่งหรือไม่? เพราะถ้าฉันวิ่ง< filenameด้วยตัวเองทุบตีจะไม่ออกมา มันจะไม่ส่งผลอะไรและคืนฉันกลับสู่พรอมต์
Stanley Yu

คำสั่งยังคงต้องการ @cuonglm เปลี่ยนแปลงข้อความเดิมของฉันจากcat < filenameการcat filenameที่ผมไม่เห็นด้วยและจะย้อนกลับ
Adam Katz

1
ไปป์เป็นไฟล์ชนิดหนึ่ง ตัวดำเนินการเชลล์|สร้างไพพ์ระหว่างสองกระบวนการย่อย (หรือบางเชลล์จากกระบวนการย่อยไปยังอินพุตมาตรฐานของเชลล์) ตัวดำเนินการเชลล์$(…)สร้างไพพ์จากกระบวนการย่อยไปยังเชลล์เอง (ไม่ใช่ไปยังอินพุตมาตรฐาน) เชลล์โอเปอเรเตอร์<ไม่เกี่ยวข้องกับไพพ์เพียงเปิดไฟล์และย้ายไฟล์ descriptor ไปยังอินพุตมาตรฐาน
Gilles 'หยุดความชั่วร้าย'

3
< fileไม่เหมือนกับcat < file(ยกเว้นในzshที่ที่มันชอบ$READNULLCMD < file) < fileเป็นอย่างดี POSIX และเพียงแค่เปิดfileสำหรับการอ่านแล้วไม่ทำอะไรเลย (เพื่อให้fileอยู่ใกล้ตรงไป) มัน$(< file)หรือ`< file`ว่าเป็นผู้ประกอบการพิเศษksh, zshและbash(และพฤติกรรมที่เหลือที่ไม่ได้ระบุใน POSIX) ดูคำตอบของฉันสำหรับรายละเอียด
Stéphane Chazelas

2
ที่จะนำความคิดเห็น @ StéphaneChazelasในแสงอื่น: ไปครั้งแรกประมาณโดยทั่วไปแล้วจะเป็นเช่นเดียวกับ$(cmd1) $(cmd2) $(cmd1; cmd2)แต่มองไปที่กรณีที่เป็นcmd2 < fileถ้าเราบอกว่า$(cmd1; < file)ไฟล์นั้นไม่ได้ถูกอ่าน แต่ด้วย$(cmd1) $(< file)คือ ดังนั้นจึงเป็นสิ่งที่ไม่ถูกต้องที่จะกล่าวว่า$(< file)เป็นเพียงกรณีสามัญของ บริษัทที่มีคำสั่งของ   $(command) เป็นกรณีพิเศษของการทดแทนคำสั่งไม่ใช่การใช้การเปลี่ยนเส้นทางตามปกติ < file$(< …)
สกอตต์

14

$(<file)(ยังทำงานร่วมกับ`<file`) เป็นผู้ประกอบการพิเศษของเปลือกกรคัดลอกโดยและzsh bashมันดูคล้ายกับการทดแทนคำสั่ง แต่มันไม่จริง

ใน POSIX เชลล์คำสั่งง่าย ๆ คือ:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

ชิ้นส่วนทั้งหมดเป็นทางเลือกคุณสามารถมีการเปลี่ยนเส้นทางเท่านั้นคำสั่งเท่านั้นกำหนดเท่านั้นหรือชุดค่าผสม

หากมีการเปลี่ยนเส้นทาง แต่ไม่มีคำสั่งการเปลี่ยนเส้นทางจะดำเนินการ (ดังนั้น> fileจะเปิดและตัดทอนfile) แต่ไม่มีอะไรเกิดขึ้น ดังนั้น

< file

เปิดfileให้อ่าน แต่ไม่มีอะไรเกิดขึ้นเนื่องจากไม่มีคำสั่ง ดังนั้นจึงfileถูกปิดและนั่นก็คือ ถ้า$(< file)เป็นการแทนคำสั่งแบบง่ายมันก็จะขยายออกเป็นไม่มีอะไรเลย

ในสเปค POSIXใน$(script)ถ้าscriptประกอบด้วยเดียวของการเปลี่ยนเส้นทางที่ก่อให้เกิดผลลัพธ์ที่ไม่ได้ระบุ นั่นคือการอนุญาตให้พฤติกรรมพิเศษของ Korn เชลล์

ใน ksh (ทดสอบที่นี่ด้วยksh93u+) หากสคริปต์ประกอบด้วยคำสั่งง่าย ๆ เพียงคำสั่งเดียว (แม้ว่าความคิดเห็นจะได้รับอนุญาตก่อนและหลัง) ที่ประกอบด้วยการเปลี่ยนเส้นทางเท่านั้น (ไม่มีคำสั่งไม่มีการมอบหมาย) และหากการเปลี่ยนเส้นทางครั้งแรกคือ stdin (fd 0) การป้อนข้อมูลเท่านั้น ( <, <<หรือ<<<) การเปลี่ยนเส้นทางเพื่อ:

  • $(< file)
  • $(0< file)
  • $(<&3)( $(0>&3)เช่นเดียวกับที่ใช้ในผู้ให้บริการรายเดียวกัน)
  • $(< file > foo 2> $(whatever))

แต่ไม่:

  • $(> foo < file)
  • ไม่ $(0<> file)
  • ไม่ $(< file; sleep 1)
  • ไม่ $(< file; < file2)

แล้วก็

  • ทั้งหมดยกเว้นการเปลี่ยนเส้นทางครั้งแรกจะถูกละเว้น (จะถูกแยกวิเคราะห์)
  • และจะขยายเนื้อหาของไฟล์ / heredoc / herestring (หรืออะไรก็ตามที่สามารถอ่านได้จาก file descriptor หากใช้สิ่งต่าง ๆ เช่น<&3) ลบอักขระขึ้นบรรทัดใหม่

ราวกับว่าใช้$(cat < file)ยกเว้นว่า

  • การอ่านทำโดยเชลล์ภายในไม่ใช่การอ่าน cat
  • ไม่มีส่วนเกี่ยวข้องกับกระบวนการหรือท่อ
  • เป็นผลมาจากข้างต้นเนื่องจากรหัสภายในไม่ได้ทำงานใน subshell การแก้ไขใด ๆ ที่ยังคงอยู่หลังจากนั้น (เช่นใน$(<${file=foo.txt})หรือ$(<file$((++n))))
  • อ่านข้อผิดพลาด (แม้ว่าจะไม่ใช่ข้อผิดพลาดในขณะที่เปิดไฟล์หรือทำซ้ำไฟล์ descriptors) จะถูกละเว้นอย่างเงียบ ๆ

ในzshก็เหมือนกันยกเว้นว่าพฤติกรรมพิเศษจะถูกเรียกเฉพาะเมื่อมีเพียงหนึ่งการเปลี่ยนเส้นทางการป้อนข้อมูลไฟล์ ( <fileหรือ0< fileไม่<&3, <<<here, < a < b... )

อย่างไรก็ตามยกเว้นเมื่อทำการจำลองเชลล์อื่น ๆ ใน:

< file
<&3
<<< here...

นั่นคือเมื่อมีการเปลี่ยนเส้นทางอินพุตเท่านั้นโดยไม่มีคำสั่งนอกการทดแทนคำสั่งzshรัน$READNULLCMD(เพจเจอร์ตามค่าเริ่มต้น) และเมื่อมีทั้งการเปลี่ยนเส้นทางอินพุตและเอาต์พุตนั้น$NULLCMD( catโดยค่าเริ่มต้น) ดังนั้นแม้ว่า$(<&3)จะไม่ได้รับการยอมรับว่าพิเศษ โอเปอเรเตอร์มันจะยังคงทำงานเหมือนเดิมkshด้วยการเรียกเพจเจอร์ให้ทำ (เพจเจอร์นั้นทำตัวเหมือนcatตั้งแต่ stdout จะเป็นไพพ์)

อย่างไรก็ตามในขณะที่ksh's $(< a < b)จะขยายไปยังเนื้อหาของaในzshจะขยายไปยังเนื้อหาของaและb(หรือเพียงแค่bถ้าmultiosตัวเลือกที่ถูกปิดใช้งาน) $(< a > b)จะคัดลอกaไปbและขยายไปไม่มีอะไร ฯลฯ

bash มีโอเปอเรเตอร์ที่คล้ายกัน แต่มีข้อแตกต่างเล็กน้อย:

  • อนุญาตความคิดเห็นก่อนหน้า แต่ไม่หลังจาก:

    echo "$(
       # getting the content of file
       < file)"

    ทำงานได้ แต่:

    echo "$(< file
       # getting the content of file
    )"

    ขยายไปสู่อะไร

  • เช่นเดียวกับในzshเพียงหนึ่งไฟล์เปลี่ยนเส้นทาง stdin แม้ว่าจะไม่มีฤดูใบไม้ร่วงกลับไป$READNULLCMDเพื่อให้$(<&3), $(< a < b)ไม่ดำเนินการเปลี่ยนเส้นทาง แต่ขยายไปยังไม่มีอะไร

  • ด้วยเหตุผลบางอย่างในขณะที่bashไม่ได้เรียกใช้catมันก็ยังคงดำเนินกระบวนการที่ฟีดเนื้อหาของไฟล์ผ่านไพพ์ทำให้การเพิ่มประสิทธิภาพน้อยกว่าเชลล์อื่น ๆ มันอยู่ในผลเหมือน$(cat < file)ที่catจะ catbuiltin
  • อันเป็นผลมาจากข้างต้นการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นภายในจะสูญหายไปในภายหลัง (ในตัวอย่างที่$(<${file=foo.txt})กล่าวถึงข้างต้นการ$fileมอบหมายนั้นหายไปในภายหลัง)

ในbash, IFS= read -rd '' var < file (ยังทำงานในzsh) เป็นวิธีที่มีประสิทธิภาพมากขึ้นในการอ่านเนื้อหาในที่ข้อความไฟล์ลงในตัวแปร นอกจากนี้ยังมีประโยชน์ในการรักษาอักขระบรรทัดใหม่ที่ตามมา ดูเพิ่มเติม$mapfile[file]ในzsh(ในzsh/mapfileโมดูลและสำหรับไฟล์ปกติเท่านั้น) ซึ่งใช้งานได้กับไฟล์ไบนารี

โปรดทราบว่าตัวแปรที่ใช้ pdksh kshมีความแตกต่างเล็กน้อยเมื่อเทียบกับ ksh93 ที่น่าสนใจในmksh(หนึ่งในเชลล์ pdksh ที่ได้มา) ใน

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

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

ที่จะพกพาไปทุกรุ่นksh, zshและbashที่ดีที่สุดคือการ จำกัด เพียง$(<file)หลีกเลี่ยงการแสดงความคิดเห็นและแบริ่งในใจว่าการปรับเปลี่ยนตัวแปรภายในอาจจะหรืออาจไม่ได้รับการเก็บรักษาไว้


ถูกต้องหรือไม่ว่า$(<)เป็นโอเปอเรเตอร์ในชื่อไฟล์ คือ<ใน $(<)ผู้ประกอบการเปลี่ยนเส้นทางหรือไม่ได้เป็นผู้ประกอบการในตัวเองและต้องเป็นส่วนหนึ่งของผู้ประกอบการทั้งหมด $(<)?
ทิม

@ เวลามันไม่สำคัญว่าคุณต้องการโทรหาพวกเขามากแค่ไหน $(<file)จะหมายถึงการขยายไปยังเนื้อหาของfileในลักษณะที่คล้ายกันเป็น$(cat < file)หากว่า วิธีการทำแตกต่างจากเปลือกหอยซึ่งอธิบายไว้ในคำตอบยาว หากคุณต้องการคุณสามารถพูดได้ว่ามันเป็นตัวดำเนินการพิเศษที่ถูกเรียกเมื่อสิ่งที่ดูเหมือนว่าการทดแทนคำสั่ง (syntactically) มีสิ่งที่ดูเหมือนว่าการเปลี่ยนเส้นทาง stdin เดียว (syntactically) แต่อีกครั้งกับ caveats และการเปลี่ยนแปลงขึ้นอยู่กับเชลล์ตามที่ระบุไว้ที่นี่ .
Stéphane Chazelas

@ StéphaneChazelas: น่าสนใจเช่นเคย; ฉันเคยทำบุ๊กมาร์กนี้แล้ว ดังนั้นn<&mและn>&mทำสิ่งเดียวกัน ฉันไม่ทราบ แต่ฉันคิดว่ามันไม่น่าแปลกใจเกินไป
สกอตต์

@Scott dup(m, n)ใช่พวกเขาทั้งสองทำ ฉันสามารถเห็นหลักฐานบางอย่างของ ksh86 โดยใช้ stdio และบางอย่างfdopen(fd, "r" or "w")ดังนั้นมันอาจมีความสำคัญ แต่การใช้ stdio ในเปลือกทำให้รู้สึกเล็กน้อยดังนั้นฉันไม่คาดหวังว่าคุณจะพบเปลือกที่ทันสมัยที่จะสร้างความแตกต่าง หนึ่งความแตกต่างก็คือว่า>&nเป็นdup(n, 1)(สั้น1>&n) ในขณะที่<&nเป็นdup(n, 0)(สั้น0<&n)
Stéphane Chazelas

ขวา. ยกเว้นแน่นอนรูปแบบสองอาร์กิวเมนต์ของการเรียกการทำซ้ำไฟล์อธิบายถูกเรียกdup2(); dup()ใช้เวลาเพียงหนึ่งอาร์กิวเมนต์และเช่นเดียวกับopen()ใช้อธิบายไฟล์ต่ำสุดที่มีอยู่ (วันนี้ฉันเรียนรู้ว่ามีdup3()ฟังก์ชั่น )
สกอตต์

8

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

นี่คือฟังก์ชั่นในการจัดการคุณสมบัตินี้ (จากbashซอร์สโค้ด, ไฟล์builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

หมายเหตุที่$(<filename)ไม่เทียบเท่ากับ$(cat filename); -หลังจะล้มเหลวถ้าชื่อไฟล์เริ่มต้นด้วยเส้นประ

$(<filename)มีพื้นเพมาจากkshและได้ถูกเพิ่มเข้าไปจากbashBash-2.02


1
cat filenameจะล้มเหลวหากชื่อไฟล์เริ่มต้นด้วยเส้นประเพราะแมวยอมรับตัวเลือก cat -- filenameคุณสามารถหลีกเลี่ยงที่ในระบบที่ทันสมัยมากที่สุดด้วย
Adam Katz

-1

คิดว่าการทดแทนคำสั่งเป็นการรันคำสั่งตามปกติและทิ้งเอาต์พุต ณ จุดที่คุณกำลังรันคำสั่ง

เอาต์พุตของคำสั่งสามารถใช้เป็นอาร์กิวเมนต์สำหรับคำสั่งอื่นเพื่อตั้งค่าตัวแปรและแม้แต่การสร้างรายการอาร์กิวเมนต์ในลูป

foo=$(echo "bar")จะตั้งค่าของตัวแปร$fooเป็นbar; echo barผลลัพธ์ของคำสั่งที่

การทดแทนคำสั่ง


1
ฉันเชื่อว่ามันค่อนข้างชัดเจนจากคำถามที่ว่า OP เข้าใจพื้นฐานของการทดแทนคำสั่ง คำถามเกี่ยวกับกรณีพิเศษของ$(< file)และเขาไม่จำเป็นต้องมีการสอนเกี่ยวกับกรณีทั่วไป หากคุณกำลังพูดว่านั่น$(< file)เป็นเพียงกรณีปกติของ$(command)คำสั่งของ< fileคุณก็กำลังพูดในสิ่งเดียวกันกับที่อดัมแคทซ์พูดและคุณผิดทั้งคู่
สกอตต์
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.