เชลล์บิวด์อินและเชลล์คีย์เวิร์ดแตกต่างกันอย่างไร?


33

เมื่อฉันเรียกใช้คำสั่งทั้งสองนี้ฉันจะได้รับ

$ type cd
cd is a shell builtin
$ type if
if is a shell keyword

มันแสดงให้เห็นอย่างชัดเจนว่าcdเป็นเชลล์ในตัวและifเป็นคำหลักของเชลล์ แล้วเชลล์บิวด์อินและคีย์เวิร์ดต่างกันอย่างไร?


คำตอบ:


45

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

builtins:

$ compgen -b
.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait                          

คำสำคัญ:

$ compgen -k
if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc              

โปรดสังเกตว่าตัวอย่างเช่น[builtin และนั่น[[คือคำหลัก ฉันจะใช้ทั้งสองนี้เพื่ออธิบายความแตกต่างด้านล่างเนื่องจากเป็นผู้ดำเนินการที่รู้จักกันดี: ทุกคนรู้จักและใช้งานเป็นประจำ (หรือควร)

คำหลักจะถูกสแกนและเข้าใจโดย Bash ในการวิเคราะห์คำ สิ่งนี้ทำให้ตัวอย่างต่อไปนี้:

string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
    echo "The string is non-empty"
fi

วิธีนี้ใช้งานได้ดีและ Bash จะได้ผลลัพธ์ที่ดี

The string is non-empty

$string_with_spacesหมายเหตุว่าผมไม่ได้พูด ในขณะที่ต่อไปนี้:

string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
    echo "The string is non-empty"
fi

แสดงให้เห็นว่า Bash ไม่มีความสุข:

bash: [: too many arguments

ทำไมมันทำงานกับคำหลักและไม่ได้อยู่กับ builtins? เพราะเมื่อ Bash วิเคราะห์คำมันจะเห็นว่า[[คำหลักใดและเข้าใจเร็วมากว่ามันพิเศษ ดังนั้นมันจะมองหาการปิด]]และจะรักษาภายในด้วยวิธีพิเศษ builtin (หรือคำสั่ง) ถือเป็นคำสั่งจริงที่จะถูกเรียกด้วยอาร์กิวเมนต์ ในตัวอย่างล่าสุดนี้ bash เข้าใจว่าควรรันคำสั่ง[ด้วยอาร์กิวเมนต์ (แสดงหนึ่งรายการต่อบรรทัด):

-n
some
spaces
here
]

ตั้งแต่การขยายตัวแปรการลบเครื่องหมายคำพูดการขยายชื่อพา ธ และการแยกคำเกิดขึ้น คำสั่ง[จะถูกสร้างขึ้นในเชลล์ดังนั้นจึงดำเนินการคำสั่งด้วยอาร์กิวเมนต์เหล่านี้ซึ่งส่งผลให้เกิดข้อผิดพลาดดังนั้นการร้องเรียน

ในทางปฏิบัติคุณจะเห็นว่าความแตกต่างนี้ช่วยให้เกิดพฤติกรรมที่ซับซ้อนซึ่งเป็นไปไม่ได้กับ builtins (หรือคำสั่ง)

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

$ a='['
$ $a -d . ]
$ echo $?
0

เมื่อ Bash วิเคราะห์คำบรรทัด$a -d . ]นั้นจะไม่เห็นสิ่งใดเป็นพิเศษ (เช่นไม่มีนามแฝงไม่มีการเปลี่ยนเส้นทางไม่มีคำหลัก) ดังนั้นมันจึงทำการขยายตัวแปร หลังจากการขยายตัวแปรมันเห็น:

[ -d . ]

เพื่อดำเนินการคำสั่ง (ในตัว) [ที่มีการขัดแย้ง-d, .และ]ที่แน่นอนคือความจริง (การทดสอบนี้เท่านั้นไม่ว่าจะ.เป็นไดเรกทอรี)

ตอนนี้ดู:

$ a='[['
$ $a -d . ]]
bash: [[: command not found

โอ้ นั่นเป็นเพราะเมื่อ Bash เห็นบรรทัดนี้จะไม่เห็นอะไรพิเศษและด้วยเหตุนี้จึงขยายตัวแปรทั้งหมดและในที่สุดก็เห็น:

[[ -d . ]]

ในขณะนี้การขยายชื่อแทนและการสแกนคำหลักได้ดำเนินการมานานแล้วและจะไม่ถูกดำเนินการอีกต่อไปดังนั้น Bash จะพยายามค้นหาคำสั่งที่เรียกว่า[[ไม่พบและบ่น

ตามบรรทัดเดียวกัน:

$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found

และ

$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found

การขยายนามแฝงเป็นสิ่งที่ค่อนข้างพิเศษเช่นกัน คุณทำสิ่งต่อไปนี้อย่างน้อยหนึ่งครั้งแล้ว:

$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

เหตุผลเหมือนกัน: การขยายนามแฝงเกิดขึ้นนานก่อนการขยายตัวแปรและการลบเครื่องหมายคำพูด


คำหลักเทียบกับนามแฝง

ตอนนี้คุณคิดว่าจะเกิดอะไรขึ้นหากเรากำหนดชื่อแทนเป็นคำหลัก

$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0

โอ้มันใช้งานได้! ดังนั้นชื่อแทนสามารถใช้กับนามแฝงคำหลักได้! ดีที่รู้.


สรุป: builtins จริงๆทำตัวเหมือนคำสั่งพวกเขาสอดคล้องกับการดำเนินการถูกดำเนินการกับการขัดแย้งที่ได้รับการขยายตัวของตัวแปรโดยตรงและแยกคำและ globbing มันเหมือนมีคำสั่งภายนอกที่ไหนสักแห่งใน/binหรือ/usr/binที่เรียกว่ามีข้อโต้แย้งที่ได้รับหลังจากการขยายตัวของตัวแปร ฯลฯ โปรดทราบว่าเมื่อฉันบอกว่ามันเหมือนกับการมีคำสั่งภายนอกที่ฉันหมายถึงเฉพาะกับข้อโต้แย้ง การขยายตัวของตัวแปร ฯลฯ บิวอินสามารถปรับเปลี่ยนสถานะภายในของเชลล์ได้!

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

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


!เป็นคำหลัก ดูเหมือนว่ามันจะเป็นไปได้ที่จะเลียนแบบพฤติกรรมของมันด้วยฟังก์ชั่น:

not() {
    if "$@"; then
        return false
    else
        return true
    fi
}

แต่สิ่งนี้จะห้ามการสร้างเช่น:

$ ! ! true
$ echo $?
0

หรือ

$ ! { true; }
echo $?
1

เช่นเดียวกันสำหรับtime: มันมีประสิทธิภาพมากกว่าที่จะมีคำหลักเพื่อให้สามารถใช้คำสั่งผสมแบบซับซ้อนและท่อที่มีการเปลี่ยนเส้นทาง:

$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null

ถ้าtimeที่คำสั่งเพียง (แม้ในตัว) ก็จะเห็นข้อโต้แย้งgrep, ^#และ/home/gniourf/.bashrcเวลานี้แล้วผลลัพธ์ที่ได้จะผ่านไปส่วนที่เหลือของท่อ แต่ด้วยคีย์เวิร์ด Bash สามารถจัดการทุกอย่างได้! มันสามารถtimeไปป์ไลน์ที่สมบูรณ์รวมถึงการเปลี่ยนเส้นทาง! หากtimeเป็นเพียงคำสั่งเราไม่สามารถทำได้:

$ time { printf 'hello '; echo world; }

ลองมัน:

$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'

ลองแก้ไข (?) มัน:

$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory

สิ้นหวัง


คำหลักกับนามแฝง?

$ alias mytime=time
$ alias myls=ls
$ mytime myls

คุณคิดว่าจะเกิดอะไรขึ้น


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


2
เห็นด้วยกับ @JohnyTex นี่เป็นหนึ่งในคำตอบที่ครอบคลุมและเหมาะสมที่สุดที่ฉันเคยเห็นในเว็บไซต์สแต็ค ขอขอบคุณ. หนึ่งคำถามที่อาจจะไม่เกี่ยวข้อง: เพียงเพราะอยากรู้อยากเห็นของฉันพยายามที่จะหาเอกสารสำหรับการทำงาน 'ปิดการใช้งานชั่วคราวนามแฝงจากคำสั่งก่อนหน้ากับ=\' โดยใช้man, aproposและhelpและฉันไม่ได้มีโชคใด ๆ ความคิดใดที่ฉันจะไปเกี่ยวกับการหาข้อมูลนี้? ส่วนใหญ่ดังนั้นในอนาคตฉันสามารถเห็นสิ่งที่อยู่ในนั้นเพราะฉันคิดว่าฉันขาดแหล่งอ้างอิง
nc

@nc: คุณจะไม่พบเอกสารอย่างชัดเจน เหตุผลที่อธิบายไว้ในคำตอบนี้ ที่อยู่ใกล้คุณจะพบคือในคู่มืออ้างอิงในส่วนเชลล์การดำเนินงาน คุณจะเห็นว่าการขยายชื่อแทนเสร็จเร็วมาก (บางสิ่งที่ฉันพยายามเน้นในคำตอบนี้) ในขั้นตอนที่ 2 การลบเครื่องหมายคำพูดการขยายพารามิเตอร์การกระจายแบบวงกลมและอื่น ๆ จะดำเนินการในภายหลัง ดังนั้นเพื่อปิดการใช้นามแฝงคุณสามารถใช้ชนิดของข้อความที่จะห้ามเปลือกจากความเข้าใจโทเค็นเป็นชื่อแทนเช่น\ll, หรือ"ll" 'll'
gniourf_gniourf

1
จริง ๆ แล้วฉันได้สิ่งที่สั่งซื้อชื่อแทนและคำหลักที่ถูกไล่ออกเกิดขึ้นก่อน เนื่องจากเราอ้างถึงการ[[ให้ผลผลิต\[[มันจึงไม่ถูกแยกวิเคราะห์เป็นนามแฝง จนถึงตอนนี้ ที่ฉันหลงทางไม่ทราบว่าแบ็กสแลชเป็นสิ่งที่อ้างถึงใน Bash และฉันพยายามค้นหามันภายใต้ชื่อแทนและคำหลักและหายไปโดยสิ้นเชิง ... ตอนนี้ดีแล้ว ภายใต้หัวข้อ Quoting:> เครื่องหมายทับขวา () ที่ไม่ใช่เครื่องหมายอัญประกาศเป็นอักขระยกเว้น
nc

1
ดังนั้นเราจึงสามารถนึกถึง (2) ในรายการ Op เป็น Tokenization และ\[[เป็นโทเค็นเดียวของประเภท LiteralQuoteToken ซึ่งตรงข้ามกับ[[ที่จะเป็น OpenTestKeywordToken และต้องมีการปิด]]CloseTestKeywordToken สำหรับ oroper compilation / syntax ที่ถูกต้อง ต่อมาใน (4) LiteralQuoteToken จะถูกประเมิน[[เป็นชื่อของ bulitin / คำสั่งที่จะดำเนินการและใน (6) Bash จะ barf เพราะไม่มี[[builtin หรือคำสั่ง ดี ฉันอาจลืมรายละเอียดที่แม่นยำเมื่อเวลาผ่านไป แต่ในขณะนี้วิธีที่ Bash ทำงานของนั้นชัดเจนสำหรับฉันมาก ขอขอบคุณ.
nc

9

man bashSHELL BUILTIN COMMANDSเรียกพวกเขา ดังนั้น "เปลือก builtin" เป็นเช่นเดียวกับคำสั่งปกติเช่นgrepฯลฯ แต่แทนที่จะถูกบรรจุอยู่ในแฟ้มแยกต่างหากก็สร้างขึ้นในทุบตีตัวเอง สิ่งนี้ทำให้พวกเขาทำงานได้อย่างมีประสิทธิภาพมากกว่าคำสั่งภายนอก

คำหลักยังเป็น "ฮาร์ดโค้ดลงทุบตี แต่ไม่เหมือนในตัวให้คำหลักที่ไม่ได้อยู่ในตัวเองคำสั่ง แต่ subunit ของสร้างคำสั่ง." ฉันตีความสิ่งนี้หมายความว่าคำหลักไม่มีฟังก์ชั่นเพียงอย่างเดียว แต่ต้องการคำสั่งเพื่อทำสิ่งใด (จากการเชื่อมโยงตัวอย่างอื่น ๆfor, while, doและ!และมีมากขึ้นในคำตอบของฉันคำถามอื่น ๆ ของคุณ.)


1
ข้อเท็จจริงที่น่าสนใจ: แต่[[ is a shell keyword [ is a shell builtinฉันมีความคิดว่าทำไมไม่มี.
Sparhawk

1
อาจเป็นเพราะเหตุผลทางประวัติศาสตร์และมาตรฐาน POSIX เพื่อให้สอดคล้องกับเชลล์เป้าหมายเก่าที่สุดเท่าที่จะเป็นไปได้เนื่องจาก[มีคำสั่งแยกต่างหากในวันนั้น [[ไม่ได้ระบุไว้ในมาตรฐานดังนั้นนักพัฒนาซอฟต์แวร์สามารถเลือกที่จะรวมไว้เป็นคำหลักหรือในตัว
Sergiy Kolodyazhnyy

1

คู่มือบรรทัดคำสั่งที่มาพร้อมกับ Ubuntu ไม่ได้ให้คำจำกัดความของคำหลักอย่างไรก็ตามคู่มือออนไลน์ (ดู sidenote) และข้อกำหนดมาตรฐานภาษาคำสั่งเชลล์ POSIX เชลล์อ้างถึงสิ่งเหล่านี้เป็น "คำสงวน" และทั้งสองมีรายการของเหล่านั้น จากมาตรฐาน POSIX:

การรับรู้นี้จะเกิดขึ้นก็ต่อเมื่อไม่มีการอ้างถึงตัวละครและเมื่อใช้คำว่า:

  • คำแรกของคำสั่ง

  • คำแรกต่อจากคำที่สงวนไว้อย่างใดอย่างหนึ่งที่ไม่ใช่แบบตัวพิมพ์สำหรับหรือใน

  • คำที่สามในคำสั่ง case (เฉพาะในที่ถูกต้องในกรณีนี้)

  • คำที่สามใน a สำหรับคำสั่ง (เฉพาะในและทำใช้ได้ในกรณีนี้)

คีย์ที่นี่คือคีย์เวิร์ด / คำสงวนมีความหมายพิเศษเพราะมันช่วยให้ไวยากรณ์ของเชลล์ทำหน้าที่ส่งสัญญาณบางอย่างของโค้ดเช่นลูปคำสั่งผสมคำสั่งการแยกย่อย (ถ้า / เคส) ฯลฯ พวกมันอนุญาตให้สร้างคำสั่งคำสั่ง แต่ ด้วยตัวเอง - ไม่ได้ทำอะไรและในความเป็นจริงถ้าคุณใส่คำหลักเช่นfor, until, case- เชลล์จะคาดว่าจะมีคำสั่งที่สมบูรณ์มิฉะนั้น - ไวยากรณ์ผิดพลาด:

$ for
bash: syntax error near unexpected token `newline'
$  

ในระดับซอร์สโค้ดคำสงวนสำหรับทุบตีจะถูกกำหนดในparese.yในขณะที่บิวด์อินมีไดเรกทอรีทั้งหมดที่ทุ่มเทให้กับพวกเขา

sidenote

ดัชนี GNU แสดง[เป็นคำที่สงวนไว้อย่างไรก็ตามเป็นคำสั่งในตัวจริง [[ตรงกันข้ามเป็นคำสงวน

ดูเพิ่มเติมที่: ความแตกต่างระหว่างคำหลักคำที่สงวนไว้และบิวด์อิน?

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