#! / bin / sh vs #! / bin / bash เพื่อความสะดวกในการพกพาสูงสุด


36

ฉันมักจะทำงานร่วมกับเซิร์ฟเวอร์ Ubuntu LTS ซึ่งจากสิ่งที่ฉันเข้าใจ symlink ไป/bin/sh /bin/dashจำนวนมากของ distros อื่น ๆ แม้ว่า symlink ไป/bin/sh/bin/bash

จากที่ฉันเข้าใจว่าถ้าสคริปต์ใช้#!/bin/shด้านบนมันอาจไม่ทำงานแบบเดียวกันบนเซิร์ฟเวอร์ทั้งหมดหรือไม่

มีแนวทางปฏิบัติที่แนะนำให้เชลล์ใช้สำหรับสคริปต์เมื่อต้องการความสะดวกในการพกพาสูงสุดของสคริปต์เหล่านั้นระหว่างเซิร์ฟเวอร์หรือไม่?


มีความแตกต่างเล็กน้อยระหว่างเปลือกหอยต่าง ๆ หากการพกพาเป็นสิ่งสำคัญที่สุดสำหรับคุณให้ใช้#!/bin/shและอย่าใช้อย่างอื่นนอกจากที่เปลือกดั้งเดิมให้ไว้
Thorbjørn Ravn Andersen

คำตอบ:


65

มีประมาณสี่ระดับของความสามารถในการพกพาสำหรับเชลล์สคริปต์ (เท่าที่เกี่ยวข้องกับบรรทัด shebang):

  1. ส่วนใหญ่พกพา: ใช้#!/bin/shshebang และใช้เพียงไวยากรณ์เปลือกพื้นฐานที่ระบุไว้ในมาตรฐาน POSIX สิ่งนี้น่าจะใช้ได้กับระบบ POSIX / unix / linux (ดียกเว้น Solaris 10 และรุ่นก่อนหน้าซึ่งมีเชลล์บอร์นแบบดั้งเดิมที่แท้จริงซึ่งนำหน้า POSIX ดังนั้นจึงไม่สอดคล้องกับ/bin/sh)

  2. พกพาได้มากที่สุดเป็นอันดับสอง: ใช้สาย Shebang #!/bin/bash(หรือ#!/usr/bin/env bash) และยึดติดกับคุณสมบัติทุบตี v3 สิ่งนี้จะทำงานบนระบบใดก็ได้ที่มีการทุบตี (ในตำแหน่งที่คาดหวัง)

  3. อุปกรณ์พกพาอันดับสาม: ใช้สาย shebang #!/bin/bash(หรือ#!/usr/bin/env bash) และใช้คุณสมบัติ bash v4 สิ่งนี้จะล้มเหลวในระบบที่มี bash v3 (เช่น macOS ซึ่งจะต้องใช้เพื่อเหตุผลด้านลิขสิทธิ์)

  4. พกพาอย่างน้อย: ใช้#!/bin/shshebang และใช้ส่วนขยาย bash กับไวยากรณ์เชลล์ POSIX สิ่งนี้จะล้มเหลวในระบบที่มีสิ่งอื่นนอกจาก bash สำหรับ / bin / sh (เช่น Ubuntu รุ่นล่าสุด) ไม่เคยทำเช่นนี้ มันไม่ใช่แค่ปัญหาความเข้ากันได้มันผิดธรรมดา น่าเสียดายที่มันเป็นข้อผิดพลาดที่ผู้คนมากมายทำกัน

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

สิ่งที่แย่ที่สุดที่คุณสามารถทำได้คือ # 4 โดยใช้ shebang ที่ไม่ถูกต้อง หากคุณไม่แน่ใจว่าฟีเจอร์ใดเป็น POSIX พื้นฐานและเป็นส่วนขยายของ bash ติดกับ bash shebang (เช่นตัวเลือก # 2) หรือทดสอบสคริปต์อย่างละเอียดด้วยเชลล์พื้นฐานมาก ๆ (เช่น dash บนเซิร์ฟเวอร์ Ubuntu LTS ของคุณ) อูบุนตูวิกิพีเดียมีรายการที่ดีของ bashisms ที่ต้องระวัง

มีข้อมูลที่ดีมากเกี่ยวกับประวัติและความแตกต่างระหว่างเชลล์ในคำถาม Unix & Linux "มันเข้ากันได้กับอะไร? และคำถาม Stackoverflow "ความแตกต่างระหว่างการดวลจุดโทษและทุบตี"

นอกจากนี้ระวังด้วยว่า shell ไม่ใช่สิ่งเดียวที่แตกต่างระหว่างระบบที่ต่างกัน หากคุณคุ้นเคยกับ linux คุณจะคุ้นเคยกับคำสั่ง GNU ซึ่งมีส่วนขยายที่ไม่เป็นมาตรฐานจำนวนมากซึ่งคุณอาจไม่พบในระบบยูนิกซ์อื่น ๆ (เช่น bsd, macOS) น่าเสียดายที่ไม่มีกฎง่ายๆที่นี่คุณเพียงแค่ต้องรู้ช่วงของการเปลี่ยนแปลงสำหรับคำสั่งที่คุณใช้

หนึ่งในคำสั่ง nastiest echoในแง่ของการพกพาเป็นหนึ่งในขั้นพื้นฐานที่สุด: เมื่อใดก็ตามที่คุณใช้กับตัวเลือกใด ๆ (เช่นecho -nหรือecho -e) หรือกับทางหนีใด ๆ (แบ็กสแลช) ในสตริงเพื่อพิมพ์เวอร์ชันที่ต่างกันจะทำสิ่งที่แตกต่างกัน เมื่อใดก็ตามที่คุณต้องการพิมพ์สตริงโดยไม่ต้องป้อนบรรทัดหลังจากนั้นหรือใช้ Escape ในสตริงให้ใช้printfแทน (และเรียนรู้วิธีการใช้งานมันมีความซับซ้อนมากกว่าecho) psคำสั่งยังไม่เป็นระเบียบ

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


12
ตัวเลือกที่สี่เป็นเพียงความคิดที่ผิด โปรดอย่าใช้มัน
2560

3
@pabouk ฉันเห็นด้วยอย่างสมบูรณ์ดังนั้นฉันจึงแก้ไขคำตอบเพื่อให้ชัดเจนยิ่งขึ้น
Gordon Davisson

1
คำสั่งแรกของคุณทำให้เข้าใจผิดเล็กน้อย มาตรฐาน POSIX ไม่ได้ระบุอะไรเกี่ยวกับ shebang นอกการใช้มันนำไปสู่พฤติกรรมที่ไม่ระบุ ยิ่งไปกว่านั้น POSIX ไม่ได้ระบุตำแหน่งที่จะต้องวางเชลล์ posix เฉพาะชื่อ ( sh) ดังนั้นจึง/bin/shไม่รับประกันว่าจะเป็นเส้นทางที่ถูกต้อง พกพามากที่สุดคือไม่ต้องระบุ Shebang ใด ๆ เลยหรือปรับ Shebang ให้เข้ากับระบบปฏิบัติการที่ใช้
jlliagre

2
เมื่อไม่นานมานี้ฉันได้พบกับบทที่ 4 ของฉันและไม่สามารถเข้าใจได้ว่าทำไมมันถึงไม่ทำงาน ท้ายที่สุดแล้วคำสั่งเดียวกันนั้นทำงานอย่างสมบูรณ์และทำในสิ่งที่ฉันต้องการให้พวกเขาทำเมื่อฉันลองพวกเขาโดยตรงในเปลือก เร็วที่สุดเท่าที่ผมเปลี่ยน#!/bin/shไป#!/bin/bashแต่สคริปต์ที่ทำงานอย่างสมบูรณ์ (เพื่อป้องกันของฉันสคริปต์ที่มีการพัฒนาอยู่ตลอดเวลาจากการหนึ่งที่จำเป็นจริงๆเท่านั้น SH-ISMS, หนึ่งที่อาศัยในทุบตีเหมือนพฤติกรรม.)
CVn

2
@ MichaelKjörling Bourne shell (จริง) แทบไม่เคยรวมอยู่กับ Linux distributions และไม่เข้ากันได้กับ POSIX เชลล์มาตรฐาน POSIX ถูกสร้างจากkshพฤติกรรมไม่ใช่บอร์น สิ่งที่ดีที่สุดลินุกซ์ต่อไป FHS ทำจะมี/bin/shเป็นสัญลักษณ์การเชื่อมโยงไปยังเปลือกที่พวกเขาเลือกที่จะให้เข้ากันได้ POSIX มักหรือbash dash
jlliagre

5

ใน./configureสคริปต์ที่เตรียมภาษา TXR สำหรับการสร้างฉันได้เขียนคำนำหน้าต่อไปนี้เพื่อการพกพาที่ดีขึ้น สคริปต์จะบู๊ตตัวเองแม้ว่า#!/bin/shจะเป็น Bourne Shell ที่ไม่สอดคล้องกับ POSIX ก็ตาม (ฉันสร้างทุกรุ่นใน Solaris 10 VM)

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell

แนวคิดในที่นี้คือเราพบเชลล์ที่ดีกว่าที่เราใช้อยู่และรันสคริปต์อีกครั้งโดยใช้เชลล์นั้น txr_shellตัวแปรสภาพแวดล้อมมีการตั้งค่าเพื่อให้สคริปต์ใหม่ดำเนินรู้ว่ามันเป็นเรื่องการดำเนินการเช่น recursive

(ในสคริปต์ของฉันtxr_shellตัวแปรนอกจากนี้ยังใช้ในภายหลังสำหรับตรงจุดประสงค์สองประการแรกมันจะมีการพิมพ์เป็นส่วนหนึ่งของข้อความให้ข้อมูลในการส่งออกของสคริปต์ประการที่สองก็มีการติดตั้งเป็น. SHELLตัวแปรในMakefileเพื่อที่makeจะใช้นี้ เปลือกเกินไปสำหรับการดำเนินการสูตร)

บนระบบที่/bin/shเป็นเส้นประคุณจะเห็นว่าตรรกะด้านบนจะค้นหา/bin/bashและเรียกใช้สคริปต์อีกครั้งด้วย

บนกล่อง Solaris 10 /usr/xpg4/bin/shจะเป็นการเตะในหากไม่พบ Bash

อารัมภบทถูกเขียนขึ้นในภาษาถิ่นอนุรักษ์นิยมใช้testสำหรับการทดสอบการมีอยู่ของไฟล์และ${@+"$@"}เคล็ดลับสำหรับการขยายข้อโต้แย้งที่จัดไว้ให้กับเชลล์เก่าที่ชำรุดบาง"$@"ตัว


ไม่มีใครต้องการxแฮ็กเกอร์ถ้ามีการใช้ข้อความที่เหมาะสมเนื่องจากสถานการณ์ที่ได้รับคำสั่งว่าสำนวนล้อมรอบการยกเลิกการร้องขอการทดสอบด้วย-aหรือ-oรวมการทดสอบหลายรายการเข้าด้วยกัน
Charles Duffy

@CharlesDuffy แน่นอน; test x$whateverว่าฉัน perpetrating มีลักษณะเหมือนหัวหอมในเคลือบเงา หากเราไม่สามารถเชื่อถือเปลือกเก่าที่ชำรุดเพื่ออ้างถึงได้${@+"$@"}ความพยายามครั้งสุดท้ายนั้นไร้ประโยชน์
Kaz

2

รูปแบบทั้งหมดของภาษาของเชลล์เป้าหมายนั้นแย่มากเมื่อเปรียบเทียบกับภาษาสคริปต์สมัยใหม่เช่น Perl, Python, Ruby, node.js และแม้แต่ Tcl (เนื้อหา) หากคุณต้องทำอะไรที่ซับซ้อนไปหน่อยคุณจะมีความสุขมากขึ้นในระยะยาวหากคุณใช้วิธีการด้านบนแทนที่จะเป็นเชลล์สคริปต์

ข้อได้เปรียบเพียงข้อเดียวที่เชลล์ภาษายังคงมีเหนือภาษาที่ใหม่กว่าคือสิ่งที่เรียกตัวเองว่า/bin/shรับประกันว่าจะมีอยู่ในทุกสิ่งที่อ้างว่าเป็นยูนิกซ์ อย่างไรก็ตามสิ่งที่อาจไม่เป็นไปตาม POSIX; หลายมรดกที่เป็นกรรมสิทธิ์ของ Unixes ทำให้เกิดความสับสนในภาษาที่นำมาใช้/bin/shและสาธารณูปโภคในเส้นทางเริ่มต้นก่อนที่จะมีการเปลี่ยนแปลงความต้องการโดย Unix95 (ใช่, Unix95, ยี่สิบปีที่ผ่านมาและนับ อาจมีชุด Unix95 หรือแม้แต่ POSIX.1-2001 หากคุณโชคดีเครื่องมือในไดเรกทอรีที่ไม่ได้อยู่ในเส้นทางเริ่มต้น (เช่น/usr/xpg4/bin) แต่ไม่มีการรับประกันว่าจะมีอยู่จริง

อย่างไรก็ตามพื้นฐานของ Perl มีแนวโน้มที่จะมีอยู่ในการติดตั้ง Unix ที่เลือกโดยพลมากกว่า Bash คือ (โดย "พื้นฐานของ Perl ว่า" ผมหมายถึง/usr/bin/perlมีอยู่และเป็นบางส่วนอาจจะค่อนข้างเก่ารุ่นของ Perl 5 และถ้าคุณโชคดีชุดของโมดูลที่มาพร้อมกับรุ่นของล่ามว่านอกจากนี้ยังมี.)

ดังนั้น:

หากคุณกำลังเขียนสิ่งที่ต้องทำงานทุกที่ที่อ้างว่าเป็น Unix (เช่นสคริปต์ "กำหนดค่า") คุณต้องใช้#! /bin/shและคุณไม่จำเป็นต้องใช้ส่วนขยายใด ๆ ทุกวันนี้ฉันจะเขียนเปลือกที่สอดคล้องกับ POSIX.1-2001 ในกรณีนี้ แต่ฉันจะเตรียมที่จะแก้ไข POSIXisms ถ้ามีคนถามหาการสนับสนุนเหล็กที่เป็นสนิม

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

(ดังนั้นเมื่อใดจึงเหมาะสมที่จะใช้ส่วนขยาย Bash เมื่อต้องการอันดับแรก: ไม่เคยลำดับที่สอง: เพียงเพื่อขยายสภาพแวดล้อมแบบโต้ตอบของ Bash - เช่นเพื่อจัดเตรียมการกรอกข้อมูลในแท็บสมาร์ทและการแจ้งแฟนซี


วันที่ผมอื่น ๆ find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -ที่มีการเขียนสคริปต์ที่ไม่ได้สิ่งที่ต้องการ มันเป็นไปไม่ได้ที่จะทำให้มันทำงานอย่างถูกต้องหากไม่มี bashisms ( read -d "") และส่วนขยาย GNU ( find -print0, tar --null) การปรับใช้บรรทัดนั้นใน Perl อีกครั้งจะนานขึ้นและงุนงงมากขึ้น นี่เป็นสถานการณ์ชนิดหนึ่งเมื่อใช้ bashisms และส่วนขยายอื่น ๆ ที่ไม่ใช่ POSIX เป็นสิ่งที่ถูกต้อง
michau

@michau มันอาจจะไม่ถือว่าเป็นหนึ่งซับอีกต่อไปขึ้นอยู่กับสิ่งที่เกิดขึ้นในวงรีเหล่านั้น แต่ฉันคิดว่าคุณไม่ได้ใช้ "เขียนสิ่งใหม่ทั้งหมดในภาษาสคริปต์ที่ดีกว่า" ตามที่ฉันหมายถึง ทางของฉันดูเหมือนperl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdประกาศว่าไม่เพียง แต่คุณไม่ต้องการ bashisms ใด ๆ แต่คุณไม่จำเป็นต้องใช้ส่วนขยาย tar GNU (หากความยาวบรรทัดคำสั่งเป็นสิ่งที่น่ากังวลหรือคุณต้องการให้การสแกนและ tar ทำงานในแบบคู่ขนานคุณจำเป็นต้องtar --null -Tใช้)
zwol

สิ่งนี้คือfindส่งคืนชื่อไฟล์มากกว่า 50,000 ชื่อในสถานการณ์ของฉันดังนั้นสคริปต์ของคุณน่าจะถึงขีดจำกัดความยาวของอาร์กิวเมนต์ การใช้งานที่ถูกต้องที่ไม่ได้ใช้ส่วนขยายที่ไม่ใช่ POSIX จะต้องสร้างเอาท์พุท tar เพิ่มขึ้นหรืออาจจะใช้ Archive :: Tar :: Stream ในโค้ด Perl แน่นอนมันสามารถทำได้ แต่ในกรณีนี้สคริปต์ Bash นั้นเร็วกว่ามากในการเขียนและมีสำเร็จรูปน้อยกว่าดังนั้นจึงมีที่ว่างสำหรับข้อบกพร่องน้อยลง
michau

BTW, ฉันสามารถใช้ Perl find ... -print0 |perl -0ne '... print ...' |tar c --null -T -แทนการทุบตีในวิธีที่รวดเร็วและรัดกุม: แต่คำถามคือ 1) ฉันใช้find -print0และtar --nullอยู่แล้วดังนั้นสิ่งที่จุดของการหลีกเลี่ยง bashism ที่read -d ""ซึ่งเป็นสิ่งชนิดเดียวกันของสิ่งที่ (ส่วนขยายของ GNU สำหรับการจัดการคั่น null ว่าแตกต่างจาก Perl อาจจะกลายเป็นสิ่งที่สักวันหนึ่ง POSIX )? 2) Perl เป็นที่แพร่หลายมากกว่า Bash หรือเปล่า ฉันใช้ภาพ Docker เมื่อเร็ว ๆ นี้และส่วนใหญ่มี Bash แต่ไม่มี Perl สคริปต์ใหม่มีแนวโน้มที่จะทำงานที่นั่นมากกว่าใน Unices โบราณ
michau
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.