เชลล์คืออินเทอร์เฟซสำหรับระบบปฏิบัติการ โดยปกติจะเป็นภาษาการเขียนโปรแกรมที่มีประสิทธิภาพมากขึ้นหรือน้อยลงตามสิทธิของตัวเอง แต่มีคุณสมบัติที่ออกแบบมาเพื่อให้ง่ายต่อการโต้ตอบกับระบบปฏิบัติการและระบบไฟล์โดยเฉพาะ ความหมายของเชลล์ POSIX (ต่อไปนี้จะเรียกว่า "เปลือก") เป็นความหมายเล็กน้อยโดยรวมคุณสมบัติบางอย่างของ LISP (s-expression มีมากเหมือนกันกับการแยกคำของเชลล์) และ C ( ไวยากรณ์ทางคณิตศาสตร์ส่วนใหญ่ของเชลล์ความหมายมาจาก C)
รากอื่น ๆ ของไวยากรณ์ของเชลล์นั้นมาจากการศึกษาเป็นส่วนหนึ่งของยูทิลิตี้ UNIX แต่ละรายการ สิ่งที่ส่วนใหญ่มักจะอยู่ในเชลล์สามารถนำไปใช้เป็นคำสั่งภายนอกได้ มันพ่น neophytes เปลือกจำนวนมากสำหรับการวนซ้ำเมื่อพวกเขารู้ว่า/bin/[
มีอยู่ในหลายระบบ
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
วัด?
สิ่งนี้สมเหตุสมผลกว่ามากหากคุณดูวิธีการใช้งานเชลล์ นี่คือการใช้งานที่ฉันทำเป็นแบบฝึกหัด มันอยู่ใน Python แต่ฉันหวังว่ามันจะไม่แฮงค์สำหรับใคร มันไม่ได้แข็งแกร่งมากนัก แต่ให้คำแนะนำ:
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
ฉันหวังว่าข้างต้นจะทำให้ชัดเจนว่ารูปแบบการดำเนินการของเชลล์นั้นค่อนข้างมาก:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
การขยายความละเอียดคำสั่งการดำเนินการ ความหมายของเชลล์ทั้งหมดถูกผูกไว้ในหนึ่งในสามสิ่งนี้แม้ว่าจะมีความสมบูรณ์มากกว่าการใช้งานที่ฉันเขียนไว้ข้างต้นก็ตาม
fork
ไม่ได้คำสั่งทั้งหมด ในความเป็นจริงมีคำสั่งจำนวนหนึ่งที่ไม่ได้มีเหตุผลมากมายที่นำไปใช้เป็นภายนอก (เช่นที่ต้องทำfork
) แต่ถึงอย่างนั้นก็มักจะมีให้ใช้เป็นภายนอกเพื่อให้สอดคล้องกับ POSIX
Bash สร้างขึ้นจากฐานนี้โดยการเพิ่มคุณสมบัติและคำสำคัญใหม่เพื่อปรับปรุงเชลล์ POSIX เกือบจะเข้ากันได้กับ sh และ bash เป็นที่แพร่หลายมากจนผู้เขียนสคริปต์บางคนใช้เวลาหลายปีโดยไม่ทราบว่าสคริปต์อาจไม่ทำงานบนระบบที่เข้มงวด POSIXly (ฉันยังสงสัยว่าผู้คนสามารถสนใจเกี่ยวกับความหมายและรูปแบบของภาษาโปรแกรมหนึ่ง ๆ ได้อย่างไรและมีเพียงเล็กน้อยสำหรับความหมายและรูปแบบของเชลล์ แต่ฉันแตกต่างกัน)
ลำดับการประเมิน
นี่เป็นคำถามหลอกลวงเล็กน้อย: Bash ตีความนิพจน์ในไวยากรณ์หลักจากซ้ายไปขวา แต่ในไวยากรณ์เลขคณิตจะเป็นไปตามลำดับความสำคัญ C นิพจน์แตกต่างจากการขยายแม้ว่า จากEXPANSION
ส่วนของคู่มือทุบตี:
ลำดับของการขยายคือ: รั้งการขยายตัว; การขยายตัวทิลเดอพารามิเตอร์และการขยายตัวแปรการขยายเลขคณิตและการแทนคำสั่ง (ทำในรูปแบบซ้ายไปขวา) การแยกคำ; และการขยายชื่อพา ธ
หากคุณเข้าใจการแยกคำการขยายชื่อพา ธ และการขยายพารามิเตอร์คุณก็พร้อมที่จะทำความเข้าใจกับสิ่งที่ bash ทำมากที่สุด โปรดทราบว่าการขยายชื่อพา ธ ที่มาหลังจากการแยกคำเป็นสิ่งสำคัญเพราะจะทำให้แน่ใจได้ว่าไฟล์ที่มีช่องว่างในชื่อจะยังคงจับคู่ได้โดย glob นี่คือเหตุผลว่าทำไมการใช้การขยาย glob ให้ดีจึงดีกว่าการแยกคำสั่งโดยทั่วไป
ขอบเขต
ขอบเขตของฟังก์ชัน
เช่นเดียวกับ ECMAscript แบบเก่าเชลล์มีขอบเขตแบบไดนามิกเว้นแต่คุณจะประกาศชื่ออย่างชัดเจนภายในฟังก์ชัน
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
สิ่งแวดล้อมและกระบวนการ "ขอบเขต"
Subshells สืบทอดตัวแปรของเชลล์พาเรนต์ แต่กระบวนการประเภทอื่นจะไม่สืบทอดชื่อที่ไม่ได้ส่งออก
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
คุณสามารถรวมกฎการกำหนดขอบเขตเหล่านี้:
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
วินัยในการพิมพ์
อืมประเภท ใช่. Bash ไม่มีประเภทจริงๆและทุกอย่างขยายเป็นสตริง (หรือบางทีคำอาจจะเหมาะสมกว่า) แต่มาดูการขยายประเภทต่างๆกัน
สตริง
สิ่งที่สวยมากสามารถถือว่าเป็นสตริง Barewords ใน bash เป็นสตริงที่มีความหมายทั้งหมดขึ้นอยู่กับการขยายที่ใช้กับมัน
ไม่มีการขยายตัว
อาจจะคุ้มค่าที่จะแสดงให้เห็นว่าคำเปล่า ๆ เป็นเพียงคำพูดจริงๆและคำพูดนั้นไม่ได้เปลี่ยนแปลงอะไรเลย
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
การขยายสตริงย่อย
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการขยายโปรดอ่านParameter Expansion
ส่วนของคู่มือ มันค่อนข้างมีพลัง
จำนวนเต็มและนิพจน์เลขคณิต
คุณสามารถฝังชื่อด้วยแอ็ตทริบิวต์จำนวนเต็มเพื่อบอกให้เชลล์ถือว่าทางด้านขวามือของนิพจน์การกำหนดเป็นเลขคณิต จากนั้นเมื่อพารามิเตอร์ขยายพารามิเตอร์จะถูกประเมินเป็นคณิตศาสตร์จำนวนเต็มก่อนที่จะขยายเป็น…สตริง
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
อาร์เรย์
อาร์กิวเมนต์และพารามิเตอร์ตำแหน่ง
ก่อนที่จะพูดถึงอาร์เรย์คุณควรพูดคุยเกี่ยวกับพารามิเตอร์ตำแหน่ง ข้อโต้แย้งเชลล์สคริปต์ที่สามารถเข้าถึงได้โดยใช้พารามิเตอร์เลข$1
, $2
, $3
ฯลฯ คุณสามารถเข้าถึงพารามิเตอร์เหล่านี้ทั้งหมดในครั้งเดียวโดยใช้"$@"
ซึ่งการขยายตัวมีหลายสิ่งหลายอย่างที่เหมือนกันกับอาร์เรย์ คุณสามารถตั้งค่าและเปลี่ยนพารามิเตอร์ตำแหน่งโดยใช้set
หรือshift
บิวด์อินหรือเพียงแค่เรียกใช้เชลล์หรือฟังก์ชันเชลล์ด้วยพารามิเตอร์เหล่านี้:
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
คู่มือทุบตีบางครั้งยังอ้างถึง$0
เป็นพารามิเตอร์ตำแหน่ง ฉันพบว่าสิ่งนี้สับสนเพราะไม่ได้รวมไว้ในจำนวนอาร์กิวเมนต์$#
แต่เป็นพารามิเตอร์ที่มีตัวเลขดังนั้นฉัน $0
คือชื่อของเชลล์หรือเชลล์สคริปต์ปัจจุบัน
อาร์เรย์
ไวยากรณ์ของอาร์เรย์ได้รับการสร้างแบบจำลองตามพารามิเตอร์ตำแหน่งดังนั้นจึงเป็นเรื่องที่ดีที่จะคิดว่าอาร์เรย์เป็น "พารามิเตอร์ตำแหน่งภายนอก" ที่ตั้งชื่อไว้หากคุณต้องการ สามารถประกาศอาร์เรย์ได้โดยใช้วิธีการต่อไปนี้:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
คุณสามารถเข้าถึงองค์ประกอบอาร์เรย์ด้วยดัชนี:
$ echo "${foo[1]}"
element1
คุณสามารถแบ่งอาร์เรย์:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
หากคุณถือว่าอาร์เรย์เป็นพารามิเตอร์ปกติคุณจะได้รับดัชนีซีโร ธ
$ echo "$baz"
element0
$ echo "$bar"
$ …
หากคุณใช้เครื่องหมายคำพูดหรือแบ็กสแลชเพื่อป้องกันการแยกคำอาร์เรย์จะรักษาการแยกคำที่ระบุไว้:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
ความแตกต่างหลักระหว่างอาร์เรย์และพารามิเตอร์ตำแหน่งคือ:
- พารามิเตอร์ตำแหน่งไม่กระจัดกระจาย หาก
$12
ตั้งค่าไว้คุณสามารถ$11
ตั้งค่าได้เช่นกัน (สามารถตั้งค่าเป็นสตริงว่างได้ แต่$#
จะไม่เล็กกว่า 12) หาก"${arr[12]}"
ตั้งค่าไว้จะไม่มีการรับประกันว่า"${arr[11]}"
ได้ตั้งค่าไว้และความยาวของอาร์เรย์อาจมีขนาดเล็กถึง 1
- องค์ประกอบซีโร ธ ของอาร์เรย์เป็นองค์ประกอบซีโร ธ ของอาร์เรย์นั้นอย่างชัดเจน ในพารามิเตอร์ตำแหน่งองค์ประกอบ zeroth ไม่ใช่อาร์กิวเมนต์แรกแต่เป็นชื่อของเชลล์หรือเชลล์สคริปต์
- เพื่ออาร์เรย์คุณต้องชิ้นและเปลี่ยนมันเหมือน
shift
arr=( "${arr[@]:1}" )
คุณสามารถทำได้เช่นunset arr[0]
กัน แต่นั่นจะทำให้องค์ประกอบแรกอยู่ที่ดัชนี 1
- อาร์เรย์สามารถใช้ร่วมกันโดยปริยายระหว่างฟังก์ชันเชลล์เป็นแบบโกลบอล แต่คุณต้องส่งผ่านพารามิเตอร์ตำแหน่งไปยังฟังก์ชันเชลล์อย่างชัดเจนเพื่อให้สามารถดูได้
มักจะสะดวกในการใช้การขยายชื่อพา ธ เพื่อสร้างอาร์เรย์ของชื่อไฟล์:
$ dirs=( */ )
คำสั่ง
คำสั่งเป็นกุญแจสำคัญ แต่ก็ครอบคลุมในเชิงลึกที่ดีกว่าที่ฉันสามารถทำได้ในคู่มือ อ่านSHELL GRAMMAR
ส่วนนี้ คำสั่งประเภทต่างๆ ได้แก่ :
- คำสั่งง่ายๆ (เช่น
$ startx
)
- ท่อ (เช่น
$ yes | make config
) (ฮ่า ๆ )
- รายการ (เช่น
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- คำสั่งผสม (เช่น
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- กระบวนการร่วม (ซับซ้อนไม่มีตัวอย่าง)
- ฟังก์ชั่น (คำสั่งผสมชื่อที่สามารถถือว่าเป็นคำสั่งง่ายๆ)
รูปแบบการดำเนินการ
รูปแบบการดำเนินการแน่นอนว่าเกี่ยวข้องกับทั้งกองและกอง นี่เป็นโรคเฉพาะของโปรแกรม UNIX ทั้งหมด ทุบตีนอกจากนี้ยังมีสาย stack สำหรับฟังก์ชั่นเปลือกมองเห็นได้ผ่านการใช้งานที่ซ้อนกันของcaller
builtin
อ้างอิง:
SHELL GRAMMAR
ส่วนของคู่มือทุบตี
- XCU เชลล์ภาษาคำสั่งเอกสาร
- ทุบตีคู่มือบนวิกิ Greycat ของ
- การเขียนโปรแกรมขั้นสูงในสภาพแวดล้อม UNIX
โปรดแสดงความคิดเห็นหากคุณต้องการให้ฉันขยายเพิ่มเติมในทิศทางที่เฉพาะเจาะจง