ความหมายสำหรับสคริปต์ Bash?


88

มากกว่าภาษาอื่น ๆ ที่ฉันรู้จักฉัน "เรียนรู้" Bash โดย Googling ทุกครั้งที่ฉันต้องการบางสิ่งเล็กน้อย ดังนั้นฉันสามารถเย็บปะติดปะต่อกันสคริปต์เล็ก ๆ น้อย ๆ ที่ดูเหมือนจะใช้งานได้ อย่างไรก็ตามฉันไม่รู้จริงๆว่าเกิดอะไรขึ้นและฉันหวังว่าจะมีการแนะนำ Bash อย่างเป็นทางการมากขึ้นในฐานะภาษาโปรแกรม ตัวอย่าง: ลำดับการประเมินคืออะไร? กฎการกำหนดขอบเขตคืออะไร? วินัยในการพิมพ์คืออะไรเช่นทุกอย่างเป็นสตริง? สถานะของโปรแกรมคืออะไร - เป็นการกำหนดคีย์ - ค่าของสตริงให้กับชื่อตัวแปรหรือไม่ มีอะไรมากกว่านั้นเช่นสแต็ก? มีกองไหม? และอื่น ๆ

ฉันคิดว่าจะศึกษาคู่มือ GNU Bash สำหรับข้อมูลเชิงลึกประเภทนี้ แต่ดูเหมือนจะไม่เป็นอย่างที่ฉันต้องการ มันเป็นรายการซักผ้าของน้ำตาลที่มีปฏิกิริยามากกว่าคำอธิบายของแบบจำลองความหมายหลัก "แบบฝึกหัดทุบตี" แบบออนไลน์นับล้านครั้งมี แต่จะแย่ลง บางทีฉันควรศึกษาก่อนshและทำความเข้าใจกับ Bash ว่าเป็นน้ำตาลที่มีปฏิกิริยาเหนือสิ่งนี้หรือไม่? ฉันไม่รู้ว่านี่เป็นโมเดลที่ถูกต้องหรือไม่

ข้อเสนอแนะใด ๆ ?

แก้ไข:ฉันถูกขอให้ยกตัวอย่างสิ่งที่ฉันต้องการ ตัวอย่างที่ค่อนข้างมากของสิ่งที่ฉันจะพิจารณา "ความหมายอย่างเป็นทางการ" คือกระดาษนี้ "สาระสำคัญของ JavaScript" บางทีอาจจะเป็นตัวอย่างเล็กน้อยไม่เป็นทางการเป็นรายงาน Haskell 2010


3
เป็นขั้นสูงทุบตี Scripting คู่มือหนึ่งใน "หนึ่งล้านบาทและ"?
choroba

2
ฉันไม่มั่นใจว่า bash มี "core semantic model" (อาจจะ "เกือบทุกอย่างเป็นสตริง"); ฉันคิดว่ามันเป็นน้ำตาลที่มีวากยสัมพันธ์จริงๆ
Gordon Davisson

4
สิ่งที่คุณเรียกว่า "รายการซักผ้าของน้ำตาลวากยสัมพันธ์" เป็นแบบจำลองความหมายของการขยายตัวซึ่งเป็นส่วนสำคัญอย่างยิ่งของการดำเนินการ 90% ของจุดบกพร่องและความสับสนเกิดจากการไม่เข้าใจโมเดลส่วนขยาย
ผู้ชายอีกคน

4
ฉันเห็นว่าทำไมบางคนอาจคิดว่านี่เป็นคำถามกว้าง ๆ ถ้าคุณอ่านแล้วเหมือนกับฉันจะเขียนเชลล์สคริปต์ได้อย่างไร แต่คำถามที่แท้จริงคืออะไรคือความหมายที่เป็นทางการและพื้นฐานสำหรับภาษาเชลล์และการทุบตีโดยเฉพาะ? และเป็นคำถามที่ดีที่มีคำตอบเดียว การลงคะแนนเพื่อเปิดอีกครั้ง
kojiro

1
ฉันได้เรียนรู้เกี่ยวกับ linuxcommand.org ค่อนข้างน้อยและยังมี pdf ฟรีของหนังสือเชิงลึกเพิ่มเติมในบรรทัดคำสั่งและการเขียนเชลล์สคริปต์
samrap

คำตอบ:


107

เชลล์คืออินเทอร์เฟซสำหรับระบบปฏิบัติการ โดยปกติจะเป็นภาษาการเขียนโปรแกรมที่มีประสิทธิภาพมากขึ้นหรือน้อยลงตามสิทธิของตัวเอง แต่มีคุณสมบัติที่ออกแบบมาเพื่อให้ง่ายต่อการโต้ตอบกับระบบปฏิบัติการและระบบไฟล์โดยเฉพาะ ความหมายของเชลล์ POSIX (ต่อไปนี้จะเรียกว่า "เปลือก") เป็นความหมายเล็กน้อยโดยรวมคุณสมบัติบางอย่างของ LISP (s-expression มีมากเหมือนกันกับการแยกคำของเชลล์) และ C ( ไวยากรณ์ทางคณิตศาสตร์ส่วนใหญ่ของเชลล์ความหมายมาจาก C)

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

$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
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:
      # We're in a child process
      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' # another way to transiently export a name
123

คุณสามารถรวมกฎการกำหนดขอบเขตเหล่านี้:

$ foo() {
>   local -x bar=123 # Export foo, but only in this scope
>   bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar

$

วินัยในการพิมพ์

อืมประเภท ใช่. Bash ไม่มีประเภทจริงๆและทุกอย่างขยายเป็นสตริง (หรือบางทีคำอาจจะเหมาะสมกว่า) แต่มาดูการขยายประเภทต่างๆกัน

สตริง

สิ่งที่สวยมากสามารถถือว่าเป็นสตริง Barewords ใน bash เป็นสตริงที่มีความหมายทั้งหมดขึ้นอยู่กับการขยายที่ใช้กับมัน

ไม่มีการขยายตัว

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

$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
การขยายสตริงย่อย
$ fail='echoes'
$ set -x # So we can see what's going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการขยายโปรดอ่านParameter Expansionส่วนของคู่มือ มันค่อนข้างมีพลัง

จำนวนเต็มและนิพจน์เลขคณิต

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

$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo # Must re-evaluate the assignment
$ echo $foo
20
$ echo "${foo:0:1}" # Still just a string
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" # Even if the zeroth index isn't set

$ …

หากคุณใช้เครื่องหมายคำพูดหรือแบ็กสแลชเพื่อป้องกันการแยกคำอาร์เรย์จะรักษาการแยกคำที่ระบุไว้:

$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2

ความแตกต่างหลักระหว่างอาร์เรย์และพารามิเตอร์ตำแหน่งคือ:

  1. พารามิเตอร์ตำแหน่งไม่กระจัดกระจาย หาก$12ตั้งค่าไว้คุณสามารถ$11ตั้งค่าได้เช่นกัน (สามารถตั้งค่าเป็นสตริงว่างได้ แต่$#จะไม่เล็กกว่า 12) หาก"${arr[12]}"ตั้งค่าไว้จะไม่มีการรับประกันว่า"${arr[11]}"ได้ตั้งค่าไว้และความยาวของอาร์เรย์อาจมีขนาดเล็กถึง 1
  2. องค์ประกอบซีโร ธ ของอาร์เรย์เป็นองค์ประกอบซีโร ธ ของอาร์เรย์นั้นอย่างชัดเจน ในพารามิเตอร์ตำแหน่งองค์ประกอบ zeroth ไม่ใช่อาร์กิวเมนต์แรกแต่เป็นชื่อของเชลล์หรือเชลล์สคริปต์
  3. เพื่ออาร์เรย์คุณต้องชิ้นและเปลี่ยนมันเหมือนshift arr=( "${arr[@]:1}" )คุณสามารถทำได้เช่นunset arr[0]กัน แต่นั่นจะทำให้องค์ประกอบแรกอยู่ที่ดัชนี 1
  4. อาร์เรย์สามารถใช้ร่วมกันโดยปริยายระหว่างฟังก์ชันเชลล์เป็นแบบโกลบอล แต่คุณต้องส่งผ่านพารามิเตอร์ตำแหน่งไปยังฟังก์ชันเชลล์อย่างชัดเจนเพื่อให้สามารถดูได้

มักจะสะดวกในการใช้การขยายชื่อพา ธ เพื่อสร้างอาร์เรย์ของชื่อไฟล์:

$ dirs=( */ )

คำสั่ง

คำสั่งเป็นกุญแจสำคัญ แต่ก็ครอบคลุมในเชิงลึกที่ดีกว่าที่ฉันสามารถทำได้ในคู่มือ อ่านSHELL GRAMMARส่วนนี้ คำสั่งประเภทต่างๆ ได้แก่ :

  1. คำสั่งง่ายๆ (เช่น$ startx)
  2. ท่อ (เช่น$ yes | make config) (ฮ่า ๆ )
  3. รายการ (เช่น$ grep -qF foo file && sed 's/foo/bar/' file > newfile)
  4. คำสั่งผสม (เช่น$ ( cd -P /var/www/webroot && echo "webroot is $PWD" ))
  5. กระบวนการร่วม (ซับซ้อนไม่มีตัวอย่าง)
  6. ฟังก์ชั่น (คำสั่งผสมชื่อที่สามารถถือว่าเป็นคำสั่งง่ายๆ)

รูปแบบการดำเนินการ

รูปแบบการดำเนินการแน่นอนว่าเกี่ยวข้องกับทั้งกองและกอง นี่เป็นโรคเฉพาะของโปรแกรม UNIX ทั้งหมด ทุบตีนอกจากนี้ยังมีสาย stack สำหรับฟังก์ชั่นเปลือกมองเห็นได้ผ่านการใช้งานที่ซ้อนกันของcallerbuiltin

อ้างอิง:

  1. SHELL GRAMMARส่วนของคู่มือทุบตี
  2. XCU เชลล์ภาษาคำสั่งเอกสาร
  3. ทุบตีคู่มือบนวิกิ Greycat ของ
  4. การเขียนโปรแกรมขั้นสูงในสภาพแวดล้อม UNIX

โปรดแสดงความคิดเห็นหากคุณต้องการให้ฉันขยายเพิ่มเติมในทิศทางที่เฉพาะเจาะจง


16
+1: คำอธิบายที่ยอดเยี่ยม ชื่นชมเวลาที่ใช้ในการเขียนสิ่งนี้พร้อมตัวอย่าง
jaypal singh

+1 สำหรับyes | make config;-) แต่จริงๆแล้วการเขียนที่ดีมาก
Digital Trauma

เพิ่งเริ่มอ่านนี่ .. สวยดี จะแสดงความคิดเห็น 1) ความประหลาดใจที่ยิ่งใหญ่กว่านั้นเกิดขึ้นเมื่อคุณเห็นสิ่งนั้น/bin/[และ/bin/testมักจะเป็นแอปพลิเคชันเดียวกัน 2) "สมมติว่าคำแรกคือคำสั่ง" - คาดหวังเมื่อคุณทำงานที่ได้รับมอบหมาย ...
Karoly Horvath

@KarolyHorvath ใช่ฉันตั้งใจยกเว้นการมอบหมายงานจากเชลล์สาธิตของฉันเนื่องจากตัวแปรมีความยุ่งยากซับซ้อน เปลือกตัวอย่างนั้นไม่ได้เขียนขึ้นโดยคำนึงถึงคำตอบนี้ - มันถูกเขียนไว้ก่อนหน้านี้มาก ฉันคิดว่าฉันสามารถสร้างมันexecleและสอดแทรกคำแรกเข้ากับสภาพแวดล้อมได้ แต่นั่นก็ยังทำให้มันซับซ้อนขึ้นอีกเล็กน้อย
kojiro

@kojiro: ไม่ว่ามันจะซับซ้อนเกินไปนั่นไม่ใช่ความตั้งใจของฉันอย่างแน่นอน! แต่การมอบหมายงานแตกต่างกันเล็กน้อย (x) และ IMHO คุณควรพูดถึงบางส่วนในข้อความ (x): และแหล่งที่มาของความสับสน ... ฉันไม่สามารถนับได้อีกต่อไปว่ากี่ครั้งแล้วที่ฉันเห็นคนที่a = 1ไม่ยอมทำงาน)
Karoly Horvath

5

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

$ A=10/2
$ echo "A = $A"           # Variable A acting like a String.
A = 10/2

$ B=1
$ let B="$B+1"            # Let is internal to bash.
$ echo "B = $B"           # One is added to B was Behaving as an integer.
B = 2

$ A=1024                  # A Defaults to string
$ B=${A/24/STRING01}      # Substitute "24"  with "STRING01".
$ echo "B = $B"           # $B STRING is a string
B = 10STRING01

$ B=${A/24/STRING01}      # Substitute "24"  with "STRING01".
$ declare -i B
$ echo "B = $B"           # Declaring a variable with non-integers in it doesn't change the contents.
B = 10STRING01

$ B=${B/STRING01/24}      # Substitute "STRING01"  with "24".
$ echo "B = $B"
B = 1024

$ declare -i B=10/2       # Declare B and assigning it an integer value
$ echo "B = $B"           # Variable B behaving as an Integer
B = 5

ประกาศความหมายของตัวเลือก:

  • -a Variable คืออาร์เรย์
  • -f ใช้ชื่อฟังก์ชันเท่านั้น
  • -i ตัวแปรจะถือว่าเป็นจำนวนเต็ม การประเมินทางคณิตศาสตร์จะดำเนินการเมื่อตัวแปรถูกกำหนดค่า
  • -p แสดงคุณลักษณะและค่าของแต่ละตัวแปร เมื่อใช้ -p ตัวเลือกเพิ่มเติมจะถูกละเว้น
  • -r ทำให้ตัวแปรเป็นแบบอ่านอย่างเดียว จากนั้นตัวแปรเหล่านี้จะไม่สามารถกำหนดค่าโดยคำสั่งการมอบหมายที่ตามมาและไม่สามารถยกเลิกการตั้งค่าได้
  • -t กำหนดคุณลักษณะการติดตามให้กับตัวแปรแต่ละตัว
  • -x ทำเครื่องหมายแต่ละตัวแปรเพื่อส่งออกไปยังคำสั่งที่ตามมาผ่านสภาพแวดล้อม

1

หน้าจัดการ bash มีข้อมูลค่อนข้างน้อยกว่าการจัดการส่วนใหญ่และมีบางสิ่งที่คุณต้องการ ข้อสันนิษฐานของฉันหลังจากใช้สคริปต์ bash มานานกว่าทศวรรษก็คือเนื่องจาก 'ประวัติเป็นส่วนขยายของ sh จึงมีไวยากรณ์ที่ขี้ขลาด (เพื่อรักษาความเข้ากันได้แบบย้อนหลังกับ sh)

FWIW ประสบการณ์ของฉันเป็นเหมือนของคุณ แม้ว่าหนังสือต่างๆ (เช่น O'Reilly "Learning the Bash Shell" และอื่น ๆ ที่คล้ายกัน) จะช่วยในเรื่องไวยากรณ์ แต่ก็มีวิธีการแก้ปัญหาต่างๆมากมายและบางเล่มก็ไม่ได้อยู่ในหนังสือเล่มนี้

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