เหตุใดจึงต้องเขียนสคริปต์ทุบตีทั้งหมดในฟังก์ชั่น


59

ที่ทำงานฉันเขียนสคริปต์ทุบตีบ่อยๆ หัวหน้างานของฉันแนะนำว่าสคริปต์ทั้งหมดจะถูกแบ่งเป็นฟังก์ชั่นคล้ายกับตัวอย่างต่อไปนี้:

#!/bin/bash

# Configure variables
declare_variables() {
    noun=geese
    count=three
}

# Announce something
i_am_foo() {
    echo "I am foo"
    sleep 0.5
    echo "hear me roar!"
}

# Tell a joke
walk_into_bar() {
    echo "So these ${count} ${noun} walk into a bar..."
}

# Emulate a pendulum clock for a bit
do_baz() {
    for i in {1..6}; do
        expr $i % 2 >/dev/null && echo "tick" || echo "tock"
        sleep 1
    done
}

# Establish run order
main() {
    declare_variables
    i_am_foo
    walk_into_bar
    do_baz
}

main

มีเหตุผลใดที่จะทำสิ่งนี้นอกเหนือจาก "การอ่าน" ซึ่งฉันคิดว่าน่าจะเป็นที่ยอมรับได้ดีพอ ๆ กับความเห็นเพิ่มเติมและระยะห่างระหว่างบรรทัดบางส่วน?

มันทำให้สคริปต์ทำงานได้อย่างมีประสิทธิภาพมากขึ้น (จริง ๆ แล้วฉันคาดหวังสิ่งตรงกันข้ามถ้ามี) หรือทำให้การแก้ไขโค้ดง่ายกว่าความสามารถในการอ่านที่กล่าวมาข้างต้นหรือไม่? หรือมันเป็นเพียงแค่การตั้งค่าโวหาร?

โปรดทราบว่าแม้ว่าสคริปต์ที่ไม่ได้แสดงให้เห็นถึงมันได้ดี "สั่งรัน" ฟังก์ชั่นในสคริปต์ที่แท้จริงของเรามีแนวโน้มที่จะเป็นเชิงเส้นมาก - walk_into_barขึ้นอยู่กับสิ่งที่i_am_fooได้ทำและdo_bazทำหน้าที่ในสิ่งที่ตั้งขึ้นโดยwalk_into_bar- เพื่อให้เป็น ความสามารถในการสลับคำสั่งเรียกใช้งานโดยพลการไม่ใช่สิ่งที่เรามักจะทำ ตัวอย่างเช่นคุณจะไม่ก็ต้องการที่จะนำdeclare_variablesหลังจากwalk_into_barที่จะทำลายสิ่ง

ตัวอย่างของวิธีที่ฉันจะเขียนสคริปต์ข้างต้นจะเป็น:

#!/bin/bash

# Configure variables
noun=geese
count=three

# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"

# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."

# Emulate a pendulum clock for a bit
for i in {1..6}; do
    expr $i % 2 >/dev/null && echo "tick" || echo "tock"
    sleep 1
done

30
ฉันชอบหัวหน้าของคุณ ในสคริปต์ของฉันฉันยังใส่main()ที่ด้านบนและเพิ่มmain "$@"ที่ด้านล่างเพื่อเรียก นั่นช่วยให้คุณเห็นตรรกะของสคริปต์ระดับสูงสิ่งแรกเมื่อคุณเปิด
John Kugelman

22
ฉันไม่เห็นด้วยกับความคิดที่ว่าการอ่านสามารถ "เป็นที่ยอมรับได้ดีพอ ๆ กับความเห็นเพิ่มเติมและระยะห่างระหว่างบรรทัด" ยกเว้นบางทีสำหรับนิยายฉันไม่ต้องการที่จะจัดการกับหนังสือที่ไม่มีสารบัญและชื่อบรรยายสำหรับแต่ละบทและหมวด ในภาษาการเขียนโปรแกรมนั่นเป็นประเภทของการอ่านที่ฟังก์ชั่นสามารถให้และความคิดเห็นไม่สามารถ
Rhymoid

6
โปรดทราบว่าควรประกาศตัวแปรที่ประกาศในฟังก์ชันซึ่งจะlocalให้ขอบเขตของตัวแปรที่มีความสำคัญอย่างมากในสคริปต์ที่ไม่สำคัญ
Boris the Spider

8
ฉันไม่เห็นด้วยกับเจ้านายของคุณ หากคุณต้องแบ่งสคริปต์ของคุณลงในฟังก์ชั่นคุณอาจไม่ควรเขียนเชลล์สคริปต์ในตอนแรก เขียนโปรแกรมแทน
el.pescado

6
ฟังก์ชั่นใช้สำหรับกระบวนการที่ทำซ้ำทั้งภายในสคริปต์หรือมากกว่าหนึ่งสคริปต์ พวกเขายังอนุญาตให้มีวิธีการแบบเดียวกันที่จะเข้าที่ เช่นการใช้ฟังก์ชั่นในการเขียนไปยัง syslog ตราบใดที่ทุกคนใช้ฟังก์ชันเดียวกันรายการ syslog ของคุณจะมีความสอดคล้องกันมากขึ้น ฟังก์ชั่นใช้งานครั้งเดียวเช่นตัวอย่างของคุณทำให้สคริปต์ไม่ซับซ้อน ในบางกรณีพวกเขาแนะนำปัญหา (การกำหนดขอบเขตตัวแปร)
Xalorous

คำตอบ:


39

ผมได้เริ่มต้นใช้รูปแบบเดียวกันในการเขียนโปรแกรมทุบตีนี้หลังจากที่ได้อ่านบล็อกโพสต์ Kfir Lavi เรื่อง "การเขียนโปรแกรมทุบตีป้องกัน" เขาให้เหตุผลที่ดีเล็กน้อย แต่โดยส่วนตัวแล้วฉันพบว่าสิ่งสำคัญที่สุดเหล่านี้:

  • โพรซีเดอร์กลายเป็นคำอธิบาย: มันง่ายกว่าที่จะเข้าใจว่าส่วนใดของรหัสที่ควรทำ แทนที่จะเป็นกำแพงรหัสคุณจะเห็น "โอ้find_log_errorsฟังก์ชั่นอ่านไฟล์บันทึกข้อผิดพลาด" เปรียบเทียบกับการค้นหาบรรทัด awk / grep / sed จำนวนมากที่ใช้ god รู้ว่าประเภทของ regex อยู่ตรงกลางของสคริปต์ที่มีความยาวคุณไม่ทราบว่ามันกำลังทำอะไรที่นั่นเว้นแต่จะมีความคิดเห็น

  • คุณสามารถแก้ปัญหาการทำงานโดยการปิดล้อมเข้ามาและset -x set +xเมื่อคุณรู้ว่าส่วนที่เหลือของรหัสทำงานเป็นไรคุณสามารถใช้เคล็ดลับนี้เพื่อมุ่งเน้นการดีบักเฉพาะฟังก์ชันที่เฉพาะเจาะจงนั้น แน่นอนคุณสามารถใส่ส่วนของสคริปต์ได้ แต่ถ้าเป็นส่วนที่มีความยาว ทำสิ่งนี้ได้ง่ายกว่า:

     set -x
     parse_process_list
     set +x
    
  • cat <<- EOF . . . EOFการใช้งานการพิมพ์ด้วย ฉันใช้มันสองสามครั้งเพื่อทำให้รหัสของฉันเป็นมืออาชีพมากขึ้น นอกจากนี้parse_args()ด้วยgetoptsฟังก์ชั่นค่อนข้างสะดวก สิ่งนี้ช่วยในการอ่านแทนการผลักทุกอย่างลงในสคริปต์เป็นกำแพงข้อความขนาดยักษ์ นอกจากนี้ยังสะดวกในการใช้ซ้ำ

และชัดเจนว่านี่เป็นสิ่งที่อ่านได้ง่ายกว่าสำหรับคนที่รู้จัก C หรือ Java หรือ Vala แต่มีประสบการณ์การใช้งานที่ จำกัด เท่าที่มีประสิทธิภาพไปมีไม่มากสิ่งที่คุณสามารถทำได้ - ทุบตีตัวเองไม่ได้เป็นภาษาที่มีประสิทธิภาพมากที่สุดและผู้คนชอบ Perl และงูหลามเมื่อมันมาถึงความเร็วและประสิทธิภาพ อย่างไรก็ตามคุณสามารถniceฟังก์ชั่น:

nice -10 resource_hungry_function

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

ในความคิดของฉันการใช้งานฟังก์ชั่นแบ็คกราวน์ก็ช่วยได้เช่นกันเมื่อคุณต้องการให้มีข้อความสั่งจำนวนมากทำงานในเบื้องหลัง

ตัวอย่างบางส่วนที่ฉันใช้สไตล์นี้:


3
ฉันไม่แน่ใจว่าคุณควรรับคำแนะนำจากบทความนั้นอย่างจริงจัง ได้รับมันมีความคิดที่ดีบางอย่าง แต่ก็ไม่ชัดเจนว่ามีคนใช้เปลือกสคริปต์ ไม่มีตัวแปรเดียวในตัวอย่างใด ๆ ที่ยกมา (!) และมันแนะนำให้ใช้ชื่อตัวแปร UPPER CASE ซึ่งมักจะเป็นความคิดที่แย่มากเนื่องจากพวกเขาสามารถขัดแย้งกับ env vars ที่มีอยู่ คะแนนของคุณในคำตอบนี้สมเหตุสมผล แต่บทความที่เชื่อมโยงดูเหมือนจะเขียนโดยคนที่เพิ่งคุ้นเคยกับภาษาอื่นและกำลังพยายามบังคับสไตล์ของพวกเขาสู่การทุบตี
terdon

1
@terdon ฉันกลับไปที่บทความแล้วอ่านอีกครั้ง ที่เดียวที่ผู้เขียนกล่าวถึงการตั้งชื่อตัวแปรตัวพิมพ์ใหญ่คือ "ตัวแปรไม่เปลี่ยนรูปโกลบอล" หากคุณพิจารณาตัวแปรทั่วโลกว่าเป็นสิ่งที่ต้องอยู่ในสภาพแวดล้อมของฟังก์ชั่นมันก็สมเหตุสมผลที่จะทำให้มันเป็นทุน ในบันทึกด้านคู่มือของทุบตีไม่ได้ระบุสถานะการประชุมสำหรับกรณีที่ตัวแปร แม้ที่นี่ ได้รับการยอมรับคำตอบว่า "มักจะ" และเพียง "มาตรฐาน" คือโดย Google ซึ่งไม่ได้เป็นตัวแทนของอุตสาหกรรมไอทีทั้งหมด
Sergiy Kolodyazhnyy

@terdon ในบันทึกอื่นฉันเห็นด้วย 100% ว่าการอ้างถึงตัวแปรควรได้รับการกล่าวถึงในบทความและมีการชี้ให้เห็นในความคิดเห็นในบล็อกด้วย นอกจากนี้ฉันจะไม่ตัดสินคนที่ใช้การเข้ารหัสในลักษณะนี้ไม่ว่าพวกเขาจะคุ้นเคยกับภาษาอื่นหรือไม่ คำถามและคำตอบทั้งหมดนี้แสดงให้เห็นอย่างชัดเจนว่ามีข้อดีและระดับของบุคคลที่คุ้นเคยกับภาษาอื่นอาจไม่เกี่ยวข้องกับที่นี่
Sergiy Kolodyazhnyy

1
@terdon ดีบทความถูกโพสต์เป็นส่วนหนึ่งของวัสดุ "แหล่งที่มา" ฉันสามารถโพสต์ทุกอย่างตามความคิดเห็นของตัวเอง แต่ฉันต้องให้เครดิตว่าบางสิ่งที่ฉันเรียนรู้จากบทความและทั้งหมดนี้มาจากการค้นคว้าเมื่อเวลาผ่านไป หน้าการเชื่อมโยงของผู้เขียนแสดงให้เห็นว่าพวกเขามีประสบการณ์ที่ดีกับ Linux และ IT โดยทั่วไปดังนั้นฉันเดาว่าบทความจะไม่แสดงสิ่งนั้นจริง ๆ แต่ฉันเชื่อมั่นในประสบการณ์ของคุณเมื่อพูดถึง Linux และ shell scripting ดังนั้นคุณอาจถูกต้อง .
Sergiy Kolodyazhnyy

1
นั่นเป็นคำตอบที่ยอดเยี่ยม แต่ฉันต้องการเพิ่มขอบเขตตัวแปรใน Bash ที่ขี้ขลาด ด้วยเหตุนี้ฉันจึงต้องการประกาศตัวแปรของฉันภายในฟังก์ชั่นที่ใช้localและเรียกทุกอย่างผ่านmain()ฟังก์ชั่น สิ่งนี้ทำให้สามารถจัดการได้มากขึ้นและคุณสามารถหลีกเลี่ยงสถานการณ์ที่ยุ่งเหยิงได้
Housni

65

การอ่านเป็นสิ่งหนึ่ง แต่มีมากกว่าmodularisationเพียงแค่นี้ ( Semi-modularisationอาจจะถูกต้องมากขึ้นสำหรับฟังก์ชั่น)

ในฟังก์ชั่นคุณสามารถเก็บตัวแปรบางตัวไว้ในเครื่องได้ซึ่งจะเพิ่มความน่าเชื่อถือลดโอกาสที่จะเกิดความยุ่งเหยิง

ฟังก์ชั่นเสริมอื่น ๆ คือการใช้งานซ้ำได้ เมื่อฟังก์ชั่นถูกเขียนโค้ดก็สามารถนำไปใช้หลายครั้งในสคริปต์ นอกจากนี้คุณยังสามารถย้ายไปยังสคริปต์อื่น

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

อีกหนึ่งจุดที่จะเพิ่ม ในฐานะที่เป็น Etsitpab Nioliv สังเกตเห็นในความคิดเห็นด้านล่างมันเป็นเรื่องง่ายที่จะเปลี่ยนเส้นทางจากฟังก์ชั่นเป็นนิติบุคคลที่สอดคล้องกัน แต่มีอีกหนึ่งแง่มุมของการเปลี่ยนเส้นทางพร้อมฟังก์ชั่น กล่าวคือการเปลี่ยนเส้นทางสามารถตั้งค่าได้ตามข้อกำหนดของฟังก์ชัน เช่น.:

f () { echo something; } > log

ตอนนี้การเรียกใช้ฟังก์ชันไม่จำเป็นต้องเปลี่ยนเส้นทางอย่างชัดเจน

$ f

การทำเช่นนี้อาจช่วยให้เกิดการซ้ำซ้อนหลายครั้งซึ่งจะเพิ่มความน่าเชื่อถืออีกครั้งและช่วยรักษาสิ่งต่าง ๆ ตามลำดับ

ดูสิ่งนี้ด้วย


55
คำตอบที่ดีมากแม้ว่ามันจะดีกว่ามากถ้ามันแบ่งออกเป็นฟังก์ชั่น
Pierre Arlaud

1
อาจเพิ่มฟังก์ชั่นที่อนุญาตให้คุณนำเข้าสคริปต์นั้นไปยังสคริปต์อื่น (โดยใช้sourceหรือ. scriptname.shและใช้ฟังก์ชันเหล่านั้นราวกับว่าอยู่ในสคริปต์ใหม่ของคุณ
SnakeDoc

นั่นครอบคลุมอยู่ในคำตอบอื่นแล้ว
Tomasz

1
ฉันขอขอบคุณที่. แต่ฉันอยากปล่อยให้คนอื่นมีความสำคัญเช่นกัน
Tomasz

7
ฉันประสบกับกรณีในวันนี้ที่ฉันต้องเปลี่ยนเส้นทางบางส่วนของสคริปต์ไปยังไฟล์ (เพื่อส่งทางอีเมล) แทนที่จะเป็นเสียงสะท้อน ฉันต้องทำ myFunction >> myFile เพื่อเปลี่ยนเส้นทางของฟังก์ชั่นที่ต้องการ ค่อนข้างสะดวก อาจมีความเกี่ยวข้อง
Etsitpab Nioliv

39

ในความคิดเห็นของฉันฉันพูดถึงสามข้อได้เปรียบของฟังก์ชั่น:

  1. ง่ายต่อการทดสอบและตรวจสอบความถูกต้อง

  2. ฟังก์ชั่นสามารถนำมาใช้ใหม่ได้อย่างง่ายดาย (ที่มา) ในสคริปต์ในอนาคต

  3. เจ้านายของคุณชอบพวกเขา

และอย่าประมาทความสำคัญของหมายเลข 3

ฉันต้องการแก้ไขปัญหาอีกหนึ่งข้อ:

... ดังนั้นความสามารถในการสับเปลี่ยนลำดับการรันโดยพลการจึงไม่ใช่สิ่งที่เราจะทำ ตัวอย่างเช่นคุณจะไม่ก็ต้องการที่จะนำdeclare_variablesหลังจากwalk_into_barที่จะทำลายสิ่ง

เพื่อให้ได้ประโยชน์จากการแบ่งรหัสลงในฟังก์ชั่นหนึ่งควรพยายามทำให้ฟังก์ชั่นเป็นอิสระที่สุด หากต้องมีตัวแปรที่ไม่ได้ใช้ที่อื่นแล้วตัวแปรที่ควรจะกำหนดไว้ในและทำให้ท้องถิ่นเพื่อwalk_into_bar walk_into_barกระบวนการแยกรหัสออกเป็นฟังก์ชั่นและลดการพึ่งพาระหว่างกันให้น้อยที่สุดควรทำให้รหัสชัดเจนและง่ายขึ้น

เป็นการดีที่ฟังก์ชั่นควรจะง่ายต่อการทดสอบเป็นรายบุคคล หากเนื่องจากการโต้ตอบพวกเขาจะไม่ง่ายที่จะทดสอบแล้วนั่นเป็นสัญญาณว่าพวกเขาอาจได้รับประโยชน์จากการ refactoring


ฉันยืนยันว่าบางครั้งมันสมเหตุสมผลในการทำแบบจำลองและบังคับใช้การพึ่งพาเหล่านั้น vs การปรับโครงสร้างใหม่เพื่อหลีกเลี่ยงพวกเขา (เนื่องจากหากมีเพียงพอและมีขนที่เพียงพอนั่นก็สามารถนำไปสู่กรณีที่สิ่งต่างๆ ฟังก์ชั่นเลย) กรณีการใช้งานที่มีความซับซ้อนมากเมื่อแรงบันดาลใจกรอบจะทำแค่นั้น
Charles Duffy

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

@ Xalorous ฉันเห็นวิธีปฏิบัติที่ตัวแปรทั่วโลกถูก เริ่มต้น ในขั้นตอนเป็นขั้นตอนกลางและรวดเร็วก่อนที่จะพัฒนากระบวนการที่อ่านค่าของพวกเขาจากไฟล์ภายนอก ... ฉันยอมรับว่ามันควรจะสะอาดกว่าเพื่อแยกนิยามและ การกำหนดค่าเริ่มต้น แต่ไม่ค่อยมีคุณต้องงอเพื่อรับประโยชน์หมายเลข 3;-)
Hastur

13

คุณแบ่งรหัสออกเป็นฟังก์ชั่นด้วยเหตุผลเดียวกับที่คุณทำสำหรับ C / C ++, python, perl, ruby ​​หรือรหัสภาษาการเขียนโปรแกรมอะไรก็ได้ เหตุผลที่ลึกกว่าคือสิ่งที่เป็นนามธรรม - คุณสรุปงานระดับล่างลงในระดับพื้นฐานดั้งเดิม (ฟังก์ชั่น) เพื่อที่คุณจะได้ไม่ต้องกังวลกับสิ่งที่ทำ ในเวลาเดียวกันรหัสจะอ่านได้ง่ายขึ้น (และสามารถบำรุงรักษาได้) และตรรกะของโปรแกรมจะชัดเจนขึ้น

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


คำตอบที่ต่ำกว่ามาตรฐาน IMHO คุณแนะนำให้ประกาศตัวแปรในmainฟังก์ชั่น / วิธีการแล้ว?
David Tabernero M.

12

ในขณะที่ฉันทั้งหมดเห็นด้วยกับนำมาใช้ใหม่ , การอ่านและประณีตจูบผู้บังคับบัญชา แต่มีหนึ่งประโยชน์อื่น ๆ ของฟังก์ชั่นในการ : ขอบเขตตัวแปร ดังที่LDP แสดง :

#!/bin/bash
# ex62.sh: Global and local variables inside a function.

func ()
{
  local loc_var=23       # Declared as local variable.
  echo                   # Uses the 'local' builtin.
  echo "\"loc_var\" in function = $loc_var"
  global_var=999         # Not declared as local.
                         # Therefore, defaults to global. 
  echo "\"global_var\" in function = $global_var"
}  

func

# Now, to see if local variable "loc_var" exists outside the function.

echo
echo "\"loc_var\" outside function = $loc_var"
                                      # $loc_var outside function = 
                                      # No, $loc_var not visible globally.
echo "\"global_var\" outside function = $global_var"
                                      # $global_var outside function = 999
                                      # $global_var is visible globally.
echo                      

exit 0
#  In contrast to C, a Bash variable declared inside a function
#+ is local ONLY if declared as such.

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

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


มีคนเพียงไม่กี่คนที่ใช้อย่างชัดเจนlocalแต่ฉันคิดว่าคนส่วนใหญ่ที่เขียนสคริปต์แยกออกเป็นฟังก์ชันยังคงปฏิบัติตามหลักการออกแบบ Usign localทำให้การแนะนำข้อบกพร่องยากขึ้น
Voo

localทำให้ตัวแปรพร้อมใช้งานสำหรับฟังก์ชันและลูก ๆ ของมันดังนั้นจึงเป็นเรื่องดีมากที่มีตัวแปรที่สามารถส่งผ่านจากฟังก์ชัน A แต่ใช้งานไม่ได้กับฟังก์ชัน B ซึ่งอาจต้องการตัวแปรที่มีชื่อเดียวกัน แต่มีวัตถุประสงค์ที่แตกต่างกัน ดังนั้นจึงเป็นสิ่งที่ดีสำหรับการกำหนดขอบเขตและตามที่ Voo พูด - ข้อผิดพลาดน้อยกว่า
Sergiy Kolodyazhnyy

10

เหตุผลที่แตกต่างอย่างสิ้นเชิงจากคำตอบอื่น ๆ ที่ให้ไว้แล้ว: เหตุผลหนึ่งที่บางครั้งใช้เทคนิคนี้โดยที่คำสั่งที่ไม่ใช่ฟังก์ชั่นคำจำกัดความที่ระดับบนสุดเป็นการเรียกmainเพื่อให้แน่ใจว่าสคริปต์ไม่ได้ตั้งใจทำสิ่งที่น่ารังเกียจ หากสคริปต์ถูกตัดทอน สคริปต์อาจถูกตัดทอนหากมีการไพพ์จากกระบวนการ A ไปยังกระบวนการ B (เชลล์) และกระบวนการ A จะยกเลิกด้วยเหตุผลใด ๆ ก็ตามก่อนที่สคริปต์จะเสร็จสิ้นการเขียนสคริปต์ทั้งหมด โดยเฉพาะอย่างยิ่งมีแนวโน้มที่จะเกิดขึ้นหากกระบวนการ A ดึงข้อมูลสคริปต์จากทรัพยากรระยะไกล ในขณะที่สำหรับเหตุผลด้านความปลอดภัยที่ไม่ใช่ความคิดที่ดีมันเป็นสิ่งที่ทำและสคริปต์บางตัวได้รับการแก้ไขเพื่อคาดการณ์ปัญหา


5
ที่น่าสนใจ! แต่ฉันพบว่ามันเป็นปัญหาที่คนเราต้องดูแลสิ่งเหล่านั้นในแต่ละโปรแกรม ในทางตรงกันข้ามmain()รูปแบบนี้เป็นปกติใน Python ที่หนึ่งใช้if __name__ == '__main__': main()ที่ส่วนท้ายของไฟล์
Martin Ueding

1
สำนวนหลามมีความได้เปรียบของการให้สคริปอื่น ๆสคริปต์ปัจจุบันโดยไม่ต้องทำงานimport mainฉันคิดว่ายามคล้ายกันสามารถใส่ในสคริปต์ทุบตี
Jake Cobb

@ Jake Cobb ใช่ ตอนนี้ฉันทำเช่นนั้นในสคริปต์ทุบตีใหม่ทั้งหมด ฉันมีสคริปต์ที่มีโครงสร้างพื้นฐานหลักของฟังก์ชั่นที่ใช้โดยสคริปต์ใหม่ทั้งหมด สคริปต์นั้นสามารถจัดหาหรือดำเนินการได้ หากมีที่มาฟังก์ชันหลักของมันจะไม่ถูกดำเนินการ การตรวจจับแหล่งที่มาของการดำเนินการ VS นั้นเกิดจากข้อเท็จจริงที่ว่า BASH_SOURCE มีชื่อของสคริปต์ที่เรียกใช้ หากเหมือนกับสคริปต์หลักสคริปต์จะถูกเรียกใช้งาน มิฉะนั้นมันจะถูกแหล่งที่มา
DocSalvager

7

กระบวนการต้องการลำดับ งานส่วนใหญ่จะเรียงตามลำดับ มันทำให้รู้สึกไม่ยุ่งกับคำสั่ง

แต่สิ่งที่ยิ่งใหญ่สุดเกี่ยวกับการเขียนโปรแกรมซึ่งรวมถึงการเขียนสคริปต์ก็คือการทดสอบ การทดสอบการทดสอบการทดสอบ สคริปต์ทดสอบใดที่คุณต้องตรวจสอบความถูกต้องของสคริปต์ในปัจจุบัน

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

แต่. จดจำรากที่เน้นกระบวนการของคุณเสมอ หากเหมาะสมที่จะมีฟังก์ชั่นเรียงตามลำดับที่โดยปกติแล้วพวกเขาจะดำเนินการแล้วทำอย่างน้อยเป็นครั้งแรกที่ผ่าน

หลังจากนั้นคุณจะเห็นว่าบางฟังก์ชั่นของคุณกำลังจัดการอินพุต, เอาต์พุตอื่น ๆ , การประมวลผลอื่น ๆ , การสร้างแบบจำลองข้อมูลอื่น ๆ , และอื่น ๆ ที่จัดการข้อมูลดังนั้นมันอาจฉลาดในการจัดกลุ่มวิธีการที่คล้ายกัน .

หลังจากนั้นคุณอาจรู้ว่าตอนนี้คุณได้เขียนไลบรารี่ของฟังก์ชั่นตัวช่วยเล็ก ๆ ที่คุณใช้ในสคริปต์จำนวนมากแล้ว


6

ความคิดเห็นและการเว้นวรรคไม่สามารถเข้าถึงได้ทุกที่ที่อยู่ใกล้ความสามารถในการอ่านที่ฉันสามารถสาธิตได้ หากไม่มีฟังก์ชั่นคุณจะไม่สามารถมองเห็นป่าไม้สำหรับต้นไม้ - ปัญหาใหญ่ ๆ ซ่อนอยู่ในรายละเอียดหลาย ๆ บรรทัด กล่าวอีกนัยหนึ่งผู้คนไม่สามารถมุ่งเน้นไปที่รายละเอียดที่ดีและในภาพรวมได้ในเวลาเดียวกัน นั่นอาจไม่ชัดเจนในสคริปต์สั้น ๆ ตราบเท่าที่ยังสั้นอยู่ก็อาจอ่านได้พอ ซอฟต์แวร์มีขนาดที่ใหญ่ขึ้นไม่เล็กลงและแน่นอนว่าเป็นส่วนหนึ่งของระบบซอฟต์แวร์ทั้งหมดของ บริษัท ของคุณ

พิจารณาถ้าฉันให้คำแนะนำแก่คุณเช่นนี้:

Place your hands on your desk.
Tense your arm muscles.
Extend your knee and hip joints.
Relax your arms.
Move your arms backwards.
Move your left leg backwards.
Move your right leg backwards.
(continue for 10,000 more lines)

ตามเวลาที่คุณผ่านไปครึ่งทางหรือแม้กระทั่ง 5% คุณจะลืมว่าขั้นตอนแรก ๆ นั้นคืออะไร คุณไม่สามารถมองเห็นปัญหาส่วนใหญ่ได้เพราะคุณไม่สามารถมองเห็นป่าเพื่อหาต้นไม้ เปรียบเทียบกับฟังก์ชั่น:

stand_up();
walk_to(break_room);
pour(coffee);
walk_to(office);

แน่นอนว่าสามารถเข้าใจได้มากขึ้นไม่ว่าคุณจะใส่ความคิดเห็นลงในรุ่นต่อเนื่องทีละบรรทัดก็ตาม นอกจากนี้ยังทำให้ห่างไกลมีโอกาสมากขึ้นคุณจะพบว่าคุณลืมที่จะทำให้กาแฟและอาจจะลืม sit_down () ในตอนท้าย เมื่อใจของคุณกำลังคิดรายละเอียดของ grep และ awk regexes คุณไม่สามารถคิดภาพใหญ่ - "จะเกิดอะไรขึ้นถ้าไม่มีกาแฟทำ"

ฟังก์ชั่นหลักช่วยให้คุณเห็นภาพรวมและสังเกตว่าคุณลืมที่จะทำกาแฟ (หรือบางคนอาจชอบดื่มชา) อีกครั้งในกรอบความคิดที่แตกต่างคุณกังวลเกี่ยวกับการใช้งานอย่างละเอียด

นอกจากนี้ยังมีประโยชน์อื่น ๆ ที่กล่าวถึงในคำตอบอื่น ๆ แน่นอน ประโยชน์อื่นที่ไม่ได้ระบุไว้อย่างชัดเจนในคำตอบอื่น ๆ คือฟังก์ชั่นให้การรับประกันที่สำคัญในการป้องกันและแก้ไขข้อบกพร่อง หากคุณพบว่าตัวแปร $ foo ในฟังก์ชันที่เหมาะสม walk_to () ผิดคุณรู้ว่าคุณต้องดูอีก 6 บรรทัดของฟังก์ชันนั้นเพื่อค้นหาทุกสิ่งที่อาจได้รับผลกระทบจากปัญหานั้นและทุกสิ่งที่สามารถทำได้ ทำให้มันผิด หากไม่มีฟังก์ชั่นที่เหมาะสมทุกสิ่งในระบบทั้งหมดอาจเป็นสาเหตุของ $ foo ที่ไม่ถูกต้องและอาจมีผลกระทบต่อทุกอย่างใน $ foo ดังนั้นคุณไม่สามารถแก้ไข $ foo ได้อย่างปลอดภัยโดยไม่ตรวจสอบโปรแกรมทุกบรรทัดอีกครั้ง ถ้า $ foo อยู่ในฟังก์ชัน


1
นี่ไม่ใช่bashไวยากรณ์ มันเป็นความอัปยศ แต่ ฉันไม่คิดว่าจะมีวิธีส่งผ่านอินพุตไปยังฟังก์ชันเช่นนั้น (เช่นpour();< coffee) มันดูเหมือนมากc++หรือphp(ฉันคิดว่า)
เสียง

2
@ tjt263 โดยไม่ต้องวงเล็บมันเป็นไวยากรณ์ซัด: เทกาแฟ ด้วย parens มันค่อนข้างทุกภาษาอื่น ๆ :)
Ray Morris

5

ความจริงเกี่ยวกับการเขียนโปรแกรม:

  • โปรแกรมของคุณจะเปลี่ยนแปลงแม้ว่าเจ้านายของคุณจะยืนยันในกรณีนี้ก็ตาม
  • รหัสและอินพุตเท่านั้นที่มีผลต่อการทำงานของโปรแกรม
  • การตั้งชื่อเป็นเรื่องยาก

ความคิดเห็นเริ่มต้นจากการเป็นช่องว่างหยุดเพราะไม่สามารถแสดงความคิดเห็นของคุณอย่างชัดเจนในโค้ด * และแย่ลง (หรือผิดพลาด) เมื่อมีการเปลี่ยนแปลง ดังนั้นหากเป็นไปได้แนวคิดแนวคิดโครงสร้างการให้เหตุผลความหมายโฟลว์การจัดการข้อผิดพลาดและสิ่งอื่น ๆ ที่เกี่ยวข้องกับความเข้าใจของโค้ดเป็นรหัส

ที่กล่าวถึงฟังก์ชั่น Bash มีปัญหาบางอย่างที่ไม่พบในภาษาส่วนใหญ่:

  • การกำหนดชื่อน่ากลัวใน Bash ตัวอย่างเช่นการลืมใช้localผลลัพธ์คำหลักในการสร้างมลภาวะเนมสเปซส่วนกลาง
  • โดยใช้local foo="$(bar)"ผลในการสูญเสียbarรหัสทางออกของ
  • ไม่มีพารามิเตอร์ที่มีชื่อดังนั้นคุณต้องจำไว้ว่าสิ่งที่มีความ"$@"หมายในบริบทที่แตกต่างกัน

* ฉันขอโทษถ้าเรื่องนี้ขัดใจ แต่หลังจากใช้ความเห็นมาหลายปีแล้วและพัฒนาโดยไม่ใช้พวกเขา ** เป็นเวลาหลายปีมันค่อนข้างชัดเจนซึ่งดีกว่า

** การใช้ข้อคิดเห็นสำหรับการให้สิทธิ์ใช้งานเอกสาร API และสิ่งที่คล้ายคลึงยังคงเป็นสิ่งจำเป็น


ผมตั้งเกือบทุกตัวแปรท้องถิ่นด้วยการประกาศให้เป็นโมฆะพวกเขาที่จุดเริ่มต้นของการทำงาน ... local foo=""จากนั้นตั้งค่าพวกเขาใช้เรียกคำสั่งให้ดำเนินการในผล foo="$(bar)" || { echo "bar() failed"; return 1; }... สิ่งนี้ทำให้เราออกจากฟังก์ชันได้อย่างรวดเร็วเมื่อไม่สามารถตั้งค่าที่ต้องการได้ การจัดฟันแบบหยิกนั้นจำเป็นเพื่อให้มั่นใจว่าreturn 1จะมีการดำเนินการเมื่อเกิดความล้มเหลวเท่านั้น
DocSalvager

5

เวลาคือเงิน

มีคำตอบที่ดีอื่น ๆที่แพร่กระจายแสงบนเหตุผลทางเทคนิคในการเขียนชุดที่สคริปต์อาจยาวในการพัฒนาสภาพแวดล้อมการทำงาน, การพัฒนาเพื่อนำมาใช้โดยกลุ่มบุคคลและไม่เพียง แต่สำหรับการใช้งานของคุณเอง

ฉันต้องการที่จะมุ่งเน้นไปที่หนึ่งคาดหวัง: ในสภาพแวดล้อมการทำงาน"เวลาคือเงิน" ดังนั้นกรณีที่ไม่มีข้อบกพร่องและการแสดงของรหัสของคุณจะได้รับการประเมินร่วมกับreadibility , การตรวจสอบ, การบำรุงรักษา, refactorability, สามารถนำมาใช้ ...

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

นอกจากนี้การเขียนใน"โมดูล"อิสระรหัส (แม้แต่สคริปสคริปต์) จะช่วยให้คุณทำงานใน"ขนาน"กับส่วนประกอบอื่น ๆ ของทีมของคุณลดเวลาในการผลิตโดยรวมและการใช้ความเชี่ยวชาญที่ดีที่สุดเพื่อทบทวนหรือเขียนส่วนใหม่ด้วย ไม่มีผลข้างเคียงกับผู้อื่นในการรีไซเคิลรหัสที่คุณเพิ่งเขียน"ตามสภาพ"สำหรับโปรแกรม / สคริปต์อื่นเพื่อสร้างไลบรารี (หรือไลบรารีของตัวอย่าง) เพื่อลดขนาดโดยรวมและโอกาสในการเกิดข้อผิดพลาดที่เกี่ยวข้องเพื่อทำการดีบักและทดสอบอย่างละเอียดในแต่ละส่วน ... และแน่นอนมันจะจัดระเบียบในส่วนตรรกะโปรแกรมของคุณ / script และเพิ่มความสามารถในการอ่าน ทุกสิ่งที่จะประหยัดเวลาและเงิน ข้อเสียคือคุณต้องยึดมาตรฐานและแสดงความคิดเห็นเกี่ยวกับหน้าที่ของคุณ (ซึ่งคุณต้องทำในสภาพแวดล้อมการทำงาน)

การยึดมั่นในมาตรฐานจะทำให้งานของคุณช้าลงในช่วงแรก แต่จะเพิ่มความเร็วในการทำงานของคนอื่น ๆ (และของคุณด้วย) ในภายหลัง แน่นอนเมื่อการทำงานร่วมกันเพิ่มจำนวนผู้คนที่เกี่ยวข้องสิ่งนี้กลายเป็นความต้องการที่หลีกเลี่ยงไม่ได้ ตัวอย่างเช่นแม้ว่าฉันเชื่อว่าตัวแปรทั่วโลกจะต้องกำหนดทั่วโลกและไม่ได้อยู่ในฟังก์ชั่นฉันสามารถเข้าใจมาตรฐานที่ inizializes พวกเขาในฟังก์ชั่นชื่อ declare_variables()เรียกว่าเสมอในบรรทัดแรกของmain()หนึ่ง ...

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

ป้อนคำอธิบายรูปภาพที่นี่

ที่นี่ด้านบนคุณจะเห็นว่ามันขยายตัวได้เพียงwalk_into_bar()ฟังก์ชั่น แม้กระทั่งอีกสายยาว 1,000 บรรทัดคุณยังสามารถควบคุมรหัสทั้งหมดได้ในหน้าเดียว หมายเหตุมันถูกพับแม้ส่วนที่คุณไปประกาศ / เริ่มต้นตัวแปร


1

นอกเหนือจากเหตุผลที่ให้ไว้ในคำตอบอื่น ๆ :

  1. จิตวิทยา: โปรแกรมเมอร์ที่มีการวัดผลการผลิตในบรรทัดของรหัสจะมีแรงจูงใจในการเขียนรหัส verbose โดยไม่จำเป็น ยิ่งผู้บริหารให้ความสำคัญกับบรรทัดของรหัสมากเท่าใดก็ยิ่งมีแรงจูงใจมากขึ้นที่โปรแกรมเมอร์จะต้องขยายโค้ดของเขาด้วยความซับซ้อนที่ไม่จำเป็น สิ่งนี้เป็นสิ่งที่ไม่พึงปรารถนาเนื่องจากความซับซ้อนที่เพิ่มขึ้นสามารถนำไปสู่ต้นทุนการบำรุงรักษาที่เพิ่มขึ้นและความพยายามที่เพิ่มขึ้นสำหรับการแก้ไขข้อบกพร่อง

มันไม่ได้เป็นคำตอบที่เลวร้ายอย่างที่ downvotes พูด หมายเหตุ: agc พูดว่านี่เป็นความเป็นไปได้และใช่มันคือ เขาไม่ได้บอกว่ามันจะเป็นความเป็นไปได้เพียงอย่างเดียวและไม่ได้กล่าวโทษใคร แม้ว่าฉันคิดว่าวันนี้เกือบจะไม่เคยได้ยินงานสัญญาจ้างแบบ "line line" -> "$$" โดยตรง แต่ในทางอ้อมหมายความว่าเป็นเรื่องธรรมดามากใช่ว่าจำนวนของรหัสที่ผลิตโดยหัวหน้า / ผู้บังคับบัญชา
user259412

0

อีกเหตุผลที่มักถูกมองข้ามก็คือการวิเคราะห์ไวยากรณ์ของ bash:

set -eu

echo "this shouldn't run"
{
echo "this shouldn't run either"

เห็นได้ชัดว่าสคริปต์นี้มีข้อผิดพลาดทางไวยากรณ์และ bash ไม่ควรรันเลยใช่ไหม ไม่ถูกต้อง.

~ $ bash t1.sh
this shouldn't run
t1.sh: line 7: syntax error: unexpected end of file

หากเราหุ้มโค้ดในฟังก์ชั่นสิ่งนี้จะไม่เกิดขึ้น:

set -eu

main() {
  echo "this shouldn't run"
  {
  echo "this shouldn't run either"
}

main
~ $ bash t1.sh
t1.sh: line 10: syntax error: unexpected end of file
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.