เมื่อฉันเรียกใช้คำสั่งทั้งสองนี้ฉันจะได้รับ
$ type cd
cd is a shell builtin
$ type if
if is a shell keyword
มันแสดงให้เห็นอย่างชัดเจนว่าcd
เป็นเชลล์ในตัวและif
เป็นคำหลักของเชลล์ แล้วเชลล์บิวด์อินและคีย์เวิร์ดต่างกันอย่างไร?
เมื่อฉันเรียกใช้คำสั่งทั้งสองนี้ฉันจะได้รับ
$ type cd
cd is a shell builtin
$ type if
if is a shell keyword
มันแสดงให้เห็นอย่างชัดเจนว่าcd
เป็นเชลล์ในตัวและif
เป็นคำหลักของเชลล์ แล้วเชลล์บิวด์อินและคีย์เวิร์ดต่างกันอย่างไร?
คำตอบ:
มีความแตกต่างอย่างมากระหว่าง 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
คุณคิดว่าจะเกิดอะไรขึ้น
จริงๆแล้วบิวอินก็เหมือนกับคำสั่งยกเว้นว่ามันสร้างขึ้นในเชลล์ในขณะที่คำสำคัญคือสิ่งที่ช่วยให้พฤติกรรมที่ซับซ้อน! เราสามารถพูดได้ว่ามันเป็นส่วนหนึ่งของไวยากรณ์ของเชลล์
=\
' โดยใช้man
, apropos
และhelp
และฉันไม่ได้มีโชคใด ๆ ความคิดใดที่ฉันจะไปเกี่ยวกับการหาข้อมูลนี้? ส่วนใหญ่ดังนั้นในอนาคตฉันสามารถเห็นสิ่งที่อยู่ในนั้นเพราะฉันคิดว่าฉันขาดแหล่งอ้างอิง
\ll
, หรือ"ll"
'll'
[[
ให้ผลผลิต\[[
มันจึงไม่ถูกแยกวิเคราะห์เป็นนามแฝง จนถึงตอนนี้ ที่ฉันหลงทางไม่ทราบว่าแบ็กสแลชเป็นสิ่งที่อ้างถึงใน Bash และฉันพยายามค้นหามันภายใต้ชื่อแทนและคำหลักและหายไปโดยสิ้นเชิง ... ตอนนี้ดีแล้ว ภายใต้หัวข้อ Quoting:> เครื่องหมายทับขวา () ที่ไม่ใช่เครื่องหมายอัญประกาศเป็นอักขระยกเว้น
\[[
เป็นโทเค็นเดียวของประเภท LiteralQuoteToken ซึ่งตรงข้ามกับ[[
ที่จะเป็น OpenTestKeywordToken และต้องมีการปิด]]
CloseTestKeywordToken สำหรับ oroper compilation / syntax ที่ถูกต้อง ต่อมาใน (4) LiteralQuoteToken จะถูกประเมิน[[
เป็นชื่อของ bulitin / คำสั่งที่จะดำเนินการและใน (6) Bash จะ barf เพราะไม่มี[[
builtin หรือคำสั่ง ดี ฉันอาจลืมรายละเอียดที่แม่นยำเมื่อเวลาผ่านไป แต่ในขณะนี้วิธีที่ Bash ทำงานของนั้นชัดเจนสำหรับฉันมาก ขอขอบคุณ.
man bash
SHELL BUILTIN COMMANDS
เรียกพวกเขา ดังนั้น "เปลือก builtin" เป็นเช่นเดียวกับคำสั่งปกติเช่นgrep
ฯลฯ แต่แทนที่จะถูกบรรจุอยู่ในแฟ้มแยกต่างหากก็สร้างขึ้นในทุบตีตัวเอง สิ่งนี้ทำให้พวกเขาทำงานได้อย่างมีประสิทธิภาพมากกว่าคำสั่งภายนอก
คำหลักยังเป็น "ฮาร์ดโค้ดลงทุบตี แต่ไม่เหมือนในตัวให้คำหลักที่ไม่ได้อยู่ในตัวเองคำสั่ง แต่ subunit ของสร้างคำสั่ง." ฉันตีความสิ่งนี้หมายความว่าคำหลักไม่มีฟังก์ชั่นเพียงอย่างเดียว แต่ต้องการคำสั่งเพื่อทำสิ่งใด (จากการเชื่อมโยงตัวอย่างอื่น ๆfor
, while
, do
และ!
และมีมากขึ้นในคำตอบของฉันคำถามอื่น ๆ ของคุณ.)
[[ is a shell keyword
[ is a shell builtin
ฉันมีความคิดว่าทำไมไม่มี.
[
มีคำสั่งแยกต่างหากในวันนั้น [[
ไม่ได้ระบุไว้ในมาตรฐานดังนั้นนักพัฒนาซอฟต์แวร์สามารถเลือกที่จะรวมไว้เป็นคำหลักหรือในตัว
คู่มือบรรทัดคำสั่งที่มาพร้อมกับ Ubuntu ไม่ได้ให้คำจำกัดความของคำหลักอย่างไรก็ตามคู่มือออนไลน์ (ดู sidenote) และข้อกำหนดมาตรฐานภาษาคำสั่งเชลล์ POSIX เชลล์อ้างถึงสิ่งเหล่านี้เป็น "คำสงวน" และทั้งสองมีรายการของเหล่านั้น จากมาตรฐาน POSIX:
การรับรู้นี้จะเกิดขึ้นก็ต่อเมื่อไม่มีการอ้างถึงตัวละครและเมื่อใช้คำว่า:
คำแรกของคำสั่ง
คำแรกต่อจากคำที่สงวนไว้อย่างใดอย่างหนึ่งที่ไม่ใช่แบบตัวพิมพ์สำหรับหรือใน
คำที่สามในคำสั่ง case (เฉพาะในที่ถูกต้องในกรณีนี้)
คำที่สามใน a สำหรับคำสั่ง (เฉพาะในและทำใช้ได้ในกรณีนี้)
คีย์ที่นี่คือคีย์เวิร์ด / คำสงวนมีความหมายพิเศษเพราะมันช่วยให้ไวยากรณ์ของเชลล์ทำหน้าที่ส่งสัญญาณบางอย่างของโค้ดเช่นลูปคำสั่งผสมคำสั่งการแยกย่อย (ถ้า / เคส) ฯลฯ พวกมันอนุญาตให้สร้างคำสั่งคำสั่ง แต่ ด้วยตัวเอง - ไม่ได้ทำอะไรและในความเป็นจริงถ้าคุณใส่คำหลักเช่นfor
, until
, case
- เชลล์จะคาดว่าจะมีคำสั่งที่สมบูรณ์มิฉะนั้น - ไวยากรณ์ผิดพลาด:
$ for
bash: syntax error near unexpected token `newline'
$
ในระดับซอร์สโค้ดคำสงวนสำหรับทุบตีจะถูกกำหนดในparese.yในขณะที่บิวด์อินมีไดเรกทอรีทั้งหมดที่ทุ่มเทให้กับพวกเขา
ดัชนี GNU แสดง[
เป็นคำที่สงวนไว้อย่างไรก็ตามเป็นคำสั่งในตัวจริง [[
ตรงกันข้ามเป็นคำสงวน
ดูเพิ่มเติมที่: ความแตกต่างระหว่างคำหลักคำที่สงวนไว้และบิวด์อิน?