ประโยชน์ของการใช้ $ () แทน backticks ในเชลล์สคริปคืออะไร?


175

มีสองวิธีในการดักจับเอาต์พุตของ command line ในbash:

  1. Legacy Bourne shell backticks ``:

    var=`command`
  2. $() ไวยากรณ์ (ซึ่งเท่าที่ฉันรู้ว่าเป็น Bash เฉพาะหรืออย่างน้อยก็ไม่ได้รับการสนับสนุนโดยกระสุนเก่าที่ไม่ใช่ POSIX เช่นบอร์นเดิม)

    var=$(command)

มีประโยชน์ในการใช้ไวยากรณ์ที่สองเมื่อเทียบกับ backticks หรือไม่ หรือเทียบเท่าสองอย่างเต็มที่ 100%


15
$()เป็น POSIX และสนับสนุนโดยบอร์นเชลล์สมัยใหม่ทั้งหมดเช่น ksh, bash, ash, dash, zsh, busybox คุณตั้งชื่อมัน (อันที่ไม่ทันสมัยก็คือโซลาริ/bin/shส แต่สำหรับโซลาริสคุณต้องแน่ใจว่าใช้โซลาริส/usr/xpg4/bin/shแทน)
Jens

27
นอกจากนี้ยังมีบันทึกเกี่ยวกับการใช้$()และ backticks ในนามแฝง หากคุณมีalias foo=$(command)ในของคุณ.bashrcแล้วcommandจะถูกดำเนินการเมื่อคำสั่งนามแฝงจะทำงานในระหว่าง.bashrcการตีความ ด้วยalias foo=`command`, commandจะถูกดำเนินการในแต่ละครั้งนามแฝงมีการเรียกใช้ แต่ถ้าคุณหลบหนี$ด้วย$()แบบฟอร์ม (เช่นalias foo=\$(command)) มันก็จะดำเนินการทุกครั้งที่นามแฝงจะถูกเรียกใช้แทนในระหว่าง.bashrcการตีความ เท่าที่ฉันสามารถบอกได้โดยการทดสอบ ฉันไม่พบอะไรใน bash docs ซึ่งอธิบายพฤติกรรมนี้
dirtside

4
@dirtside เชลล์ตัวไหนที่ฉันได้ทำการทดสอบ bash และ POSIX shell, backtick นั้นจะถูกประมวลผลเมื่อฉันมา ตัวอย่างง่ายๆ: alias curDate = `date` หลังจากที่ฉันหามันและเรียกใช้ curDate แล้วฉันจะได้รับข้อความมันไม่สามารถหาคำสั่ง Mon (แหล่งที่มาในวันจันทร์) ได้
thecarpy

@daydide มันไม่เป็นความจริง แม้จะใช้ alias foo = `command` commandก็สามารถทำได้เพียงครั้งเดียว ฉันตรวจสอบแล้ว: function aaa () {วันที่ printf; echo aaa >> ~ / test.txt; } นามแฝง test1 aaa= ฟังก์ชัน aaa ดำเนินการเพียงครั้งเดียว (หลังจากเข้าสู่ระบบทุกครั้ง) ไม่ว่าจะดำเนินการนามแฝงกี่ครั้งtest1ก็ตาม ฉันใช้. bashrc (บน Debian 10)
vitaliydev

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

คำตอบ:


156

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

ตัวอย่างแม้ว่าจะถูกจัดทำขึ้นบ้าง:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

ซึ่งจะให้รายชื่อของไฟล์ทั้งหมดในส่วน/dirต้นไม้ไดเรกทอรีที่มีชื่อเดียวกับไฟล์ข้อความลงวันที่เก่าแก่ที่สุดจากธันวาคม 2011 (ก)

อีกตัวอย่างจะเป็นสิ่งที่ต้องการรับชื่อ (ไม่ใช่พา ธ เต็ม) ของไดเรกทอรีหลัก:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a)ตอนนี้คำสั่งที่เฉพาะเจาะจงอาจใช้งานไม่ได้จริงฉันไม่ได้ทดสอบการใช้งาน ดังนั้นถ้าคุณลงคะแนนให้ฉันคุณได้เห็นว่าเจตนา :-) มันมีความหมายเพียงแค่เป็นภาพประกอบว่าคุณสามารถทำรังได้อย่างไรไม่ใช่ตัวอย่างที่พร้อมสำหรับการผลิตที่ปราศจากข้อบกพร่อง


87
ฉันคาดหวังว่ารหัสทั้งหมดใน SO นั้นจะเป็นตัวอย่างพร้อมการผลิตที่ออกแบบมาเพื่อมาตรฐานความน่าเชื่อถือของรหัสรถรับส่งของ NASA สิ่งใดที่น้อยลงจะได้รับการตั้งค่าสถานะและลบการลงคะแนน
DVK

1
@DVK ในกรณีที่คุณไม่ได้ล้อเล่นฉันไม่เห็นด้วยว่าการมีส่วนร่วมของรหัสควรถูกตั้งค่าสถานะหากไม่ได้รับการยกเว้นจากค่าเริ่มต้นของ SO โดยอัตโนมัติ (ใบอนุญาต CC-BY-SA หรือ MIT) เพื่อรับรองการรับประกันหรือความเหมาะสม ฉันจะนำโค้ดกลับมาใช้ใหม่บน SO ด้วยความเสี่ยงของตัวเองและลงคะแนนสนับสนุนตามความช่วยเหลือประโยชน์ด้านเทคนิค ฯลฯ
chrstphrchvz

9
@chrstphrchvz ถ้าคุณมองไปที่โปรไฟล์ของฉันคุณจะเห็นข้อมูลโค้ดนี้น้อย: "ทั้งหมดที่โพสต์รหัสฉันในกองมากเกินจะถูกปกคลุมด้วย 'การทำสิ่งที่ห่าที่คุณต้องการด้วย' ใบอนุญาตฉบับเต็มซึ่งเป็น: Do whatever the heck you want with it." :-) ไม่ว่าในกรณีใดฉันค่อนข้างแน่ใจว่ามันเป็นอารมณ์ขันจาก DVK
paxdiablo

1
แต่ถ้าเป็น "cd / home / pax / xyzzy / plover" ล่ะ? คุณจะพบว่าตัวเองอยู่ในเขาวงกตของทางเดินเล็ก ๆ ที่แตกต่างกันหรือไม่?
Duncan

@wchargin คุณรู้ไหมว่าเหตุการณ์นี้เป็นตัวกระตุ้นหลักสำหรับการเพิ่มตัวอักษรที่กำหนดไว้ในภาษา C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod

59

สมมติว่าคุณต้องการค้นหาไดเร็กทอรี lib ที่สอดคล้องกับตำแหน่งที่gccติดตั้ง คุณมีทางเลือก:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

อันแรกง่ายกว่าอันที่สอง - ใช้อันแรก


4
มันจะเป็นการดีถ้าได้เห็นเครื่องหมายคำพูดรอบ ๆ การแทนที่คำสั่งเหล่านั้น!
Tom Fenech

1
อย่างน้อยสำหรับ bash ความคิดเห็นของ @TomFenech ใช้ไม่ได้กับการมอบหมาย ประพฤติตัวเช่นเดียวกับx=$(f); x=`f` x="$(f)"; x="`f`"ในทางตรงกันข้ามการกำหนดอาเรย์x=($(f)); x=(`f`) จะดำเนินการแยกที่$IFSตัวละครตามที่คาดไว้เมื่อเรียกใช้คำสั่ง สะดวก ( x=1 2 3 4ไม่สมเหตุสมผล) แต่ไม่สอดคล้องกัน
kdb

@ kdb คุณพูดถูกเกี่ยวกับการx=$(f)ทำงานโดยไม่ต้องใส่เครื่องหมายคำพูด ฉันควรจะเจาะจงมากขึ้น; ฉันกำลังเสนอให้ใช้libdir=$(dirname "$(dirname "$(which gcc)")")/lib(อัญประกาศแทนการแทนที่คำสั่งภายใน ) หากไม่มีการพูดถึงคุณจะยังคงต้องแยกคำและขยายแบบปกติ
Tom Fenech

38

backticks ( `...`) เป็นไวยากรณ์ดั้งเดิมที่ต้องการโดยเฉพาะ bourne-shell ที่เก่าแก่ที่สุดที่ไม่รองรับ POSIX และ$(...)เป็น POSIX และเป็นที่นิยมมากกว่าด้วยเหตุผลหลายประการ:

  • แบ็กสแลช ( \) ภายใน backticks ได้รับการจัดการในลักษณะที่ไม่ชัดเจน:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • ข้อความที่ซ้อนกันภายใน$()มีความสะดวกมากขึ้น:

    echo "x is $(sed ... <<<"$y")"

    แทน:

    echo "x is `sed ... <<<\"$y\"`"

    หรือเขียนสิ่งที่ชอบ:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    เพราะ$()ใช้บริบทใหม่ทั้งหมดสำหรับการอ้างอิง

    ซึ่งไม่สามารถพกพาได้เนื่องจากกระสุน Bourne และ Korn จะต้องใช้แบ็กสแลชเหล่านี้ในขณะที่ Bash และ Dash จะไม่ทำงาน

  • ไวยากรณ์สำหรับการแทนที่คำสั่งซ้อนได้ง่ายขึ้น:

    x=$(grep "$(dirname "$path")" file)

    กว่า:

    x=`grep "\`dirname \"$path\"\`" file`

    เนื่องจาก$()บังคับใช้บริบทใหม่ทั้งหมดสำหรับการอ้างอิงดังนั้นการทดแทนคำสั่งแต่ละรายการจะได้รับการคุ้มครองและสามารถปฏิบัติได้ด้วยตนเองโดยไม่ต้องกังวลเป็นพิเศษเกี่ยวกับการอ้างอิงและการหลบหนี เมื่อใช้ backticks มันจะได้รับ uglier และ uglier หลังจากระดับที่สองขึ้นไป

    อีกไม่กี่ตัวอย่าง:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • มันแก้ปัญหาพฤติกรรมที่ไม่สอดคล้องกันเมื่อใช้ backquotes:

    • echo '\$x' เอาท์พุท \$x
    • echo `echo '\$x'` เอาท์พุท $x
    • echo $(echo '\$x') เอาท์พุท \$x
  • ไวยากรณ์ Backticks มีข้อ จำกัด ทางประวัติศาสตร์เกี่ยวกับเนื้อหาของคำสั่งแบบฝังและไม่สามารถจัดการกับสคริปต์ที่ถูกต้องซึ่งรวมถึง backquotes ในขณะที่$()รูปแบบใหม่สามารถประมวลผลสคริปต์ฝังตัวที่ถูกต้อง

    ตัวอย่างเช่นสคริปต์ฝังตัวที่ถูกต้องมิฉะนั้นจะไม่ทำงานในคอลัมน์ด้านซ้าย แต่ทำงานบนIEEEด้านขวา:

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

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

ที่มา: ทำไมจึงเป็น$(...)ที่ต้องการมากกว่า`...`(backticks) ที่ BashFAQ

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


1
ข้อความที่ซ้อนกันในการแทนที่สไตล์กราเวียสำเนียงนั้นไม่ได้กำหนดจริงๆคุณสามารถใช้เครื่องหมายคำพูดคู่ทั้งภายนอกหรือภายในแต่ไม่ใช่ทั้งสองแบบพกพา เชลล์ตีความพวกมันต่างกัน บางคนต้องการแบ็กสแลชเพื่อหนีพวกเขาบางคนต้องการแบ็กสแลชที่ไม่ได้ใช้ Escape
mirabilos

23

จากคนทุบตี:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.

9

นอกจากคำตอบอื่น ๆ แล้ว

$(...)

โดดเด่นกว่าสายตาดีกว่า

`...`

Backticks ดูเหมือน apostrophes มากเกินไป สิ่งนี้จะแตกต่างกันไปตามแบบอักษรที่คุณใช้

(และอย่างที่ฉันเพิ่งสังเกตเห็น backticks เป็นเรื่องยากมากในการป้อนตัวอย่างโค้ดแบบอินไลน์)


2
คุณต้องมีแป้นพิมพ์แปลก ๆ (หรือฉันจะทำอย่างไร) สำหรับฉันมันง่ายกว่ามากในการพิมพ์ backticks - เป็นปุ่มมุมบนซ้ายไม่จำเป็นต้องใช้ SHIFT หรือ ALT
DVK

1
@DVK: ฉันกำลังพูดถึงลักษณะของพวกเขาไม่สะดวกในการพิมพ์ (แป้นพิมพ์ของฉันอาจเหมือนกับของคุณ) ถึงกระนั้นตอนนี้ที่คุณพูดถึงฉันคิดว่าฉันมีหน่วยความจำของกล้ามเนื้อดีขึ้น$ (และ)กว่าที่ฉันทำเพื่อ backtick; YMMV
Keith Thompson

ไม่เคยโปรแกรมในทุบตี (ข้ามจาก ksh เก่า Perl) เพื่อให้แน่นอนหน่วยความจำสำหรับไวยากรณ์เฉพาะที่ไม่มี :)
DVK

1
@DVK ฉันคิดว่า Keith อ้างถึงความจริงที่ว่าไม่ใช่รหัสบล็อกที่นี่ (block code หมายถึงการใช้ช่องว่างสี่จุดที่จุดเริ่มต้นของบรรทัด) ใช้ backticks เพื่อระบุว่าเป็นการยากที่จะใส่ backticks ในพวกเขาภาพประกอบอื่นของ ปัญหาในการซ้อน :-) FWIW คุณอาจพบรหัสและ / แท็กรหัส (วิธีอื่นในการทำโค้ดที่ไม่ใช่บล็อก) สามารถมี backticks ได้ง่ายขึ้น
paxdiablo

@Pax - ได้รับมัน ดุจ! แน่นอนว่าฉันติดอยู่บนบล็อกบล็อคด้วยเหตุผลบางอย่าง
DVK

7

$() อนุญาตให้ทำรัง

out=$(echo today is $(date))

ฉันคิดว่า backticks ไม่อนุญาต


2
คุณสามารถซ้อน backticks; มันยากกว่ามาก: out=`echo today is \`date\`` .
Jonathan Leffler

4

มันเป็นมาตรฐาน POSIX ที่กำหนด$(command)รูปแบบของการทดแทนคำสั่ง เชลล์ที่ใช้งานในปัจจุบันส่วนใหญ่เป็นไปตาม POSIX และสนับสนุนรูปแบบที่ต้องการนี้มากกว่าสัญกรณ์ backtick โบราณ ส่วนการทดแทนคำสั่ง (2.6.3) ของเอกสารภาษาเชลล์อธิบายสิ่งนี้:

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

$(command)

หรือ (เวอร์ชันที่ยกมาหลัง):

`command`

เชลล์จะขยายการทดแทนคำสั่งโดยการดำเนินการคำสั่ง ในสภาพแวดล้อม subshell (ดูที่Shell Execution Environment ) และแทนที่การแทนที่คำสั่ง (ข้อความของคำสั่งรวมถึงการปิด "$ ()" หรือ backquotes) ด้วยเอาต์พุตมาตรฐานของคำสั่ง ลำดับของ<newline>อักขระหนึ่งตัวขึ้นไปเมื่อสิ้นสุดการแทนที่ <newline>อักขระฝังตัวก่อนสิ้นเอาต์พุตจะไม่ถูกลบออก อย่างไรก็ตามอาจถูกใช้เป็นตัวคั่นฟิลด์และตัดออกในระหว่างการแยกฟิลด์ขึ้นอยู่กับค่าของ IFS และการอ้างอิงที่มีผล หากผลลัพธ์มีไบต์เป็นโมฆะพฤติกรรมจะไม่ได้รับการกำหนด

ภายในสไตล์ backquoted ของแทนคำสั่ง<backslash>จะต้องรักษาความหมายที่แท้จริงของมันยกเว้นเมื่อตามด้วย: ' $', ' `' <backslash>หรือ การค้นหา backquote ที่ตรงกันจะต้องเป็นไปตาม backquote ที่ไม่มีการ Escape แรก ในระหว่างการค้นหานี้หากพบ backquote ที่ไม่ใช้ Escape ภายในข้อคิดเห็นเชลล์, here-document, การแทนที่คำสั่งแบบฝังของแบบฟอร์ม$ ( คำสั่ง ) หรือสตริงที่ยกมา, ผลลัพธ์ที่ไม่ได้กำหนดจะเกิดขึ้น สตริงที่มีเครื่องหมายคำพูดเดี่ยวหรือเครื่องหมายคำพูดคู่ที่เริ่มต้น แต่ไม่สิ้นสุดภายใน`...`ลำดับ "" จะสร้างผลลัพธ์ที่ไม่ได้กำหนด

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

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

การทดแทนคำสั่งสามารถซ้อนกันได้ ในการระบุการซ้อนภายในเวอร์ชัน backquote แอปพลิเคชันจะต้องนำหน้า backquotes ด้านในด้วย<backslash>อักขระ ตัวอย่างเช่น:

\`command\`

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

$( (command) )


4

นี่เป็นคำถามเดิม แต่ฉันมากับตัวอย่างที่ถูกต้องสมบูรณ์ของมากกว่า$(...)`...`

ฉันใช้เดสก์ท็อประยะไกลกับ windows ที่ใช้ cygwin และต้องการย้ำผ่านผลลัพธ์ของคำสั่ง น่าเศร้าที่ตัวละคร backtick นั้นเป็นไปไม่ได้ที่จะเข้าไปไม่ว่าจะเป็นเดสก์ท็อประยะไกลหรือตัว Cygwin

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

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