ทดแทนสองและสามในทุบตีและ zsh


23

ติดตามส่วนพื้นหลังในคำถามนี้

ในbashที่ฉันสามารถใช้เพื่อทดแทนคู่ใน${!FOO} zsh ${(P)FOO}ทั้งในโรงเรียนเก่า (แฮ็ค - y) eval \$$FOOทำงาน

ดังนั้นสิ่งที่ฉลาดและสมเหตุสมผลที่สุดสำหรับฉันคือ${${FOO}}, ${${${FOO}}}…การทดแทนสอง / สาม / n เหตุใดจึงไม่ทำงานตามที่คาดไว้

ประการที่สอง: สิ่งที่\ทำในevalคำสั่ง? ฉันคิดว่ามันเป็นการหลบหนีทำให้บางอย่างeval \$$$FOOเป็นไปไม่ได้ จะทำการทดแทนแบบสาม / n ด้วยสิ่งนั้นได้อย่างไรในทุกเชลล์?

คำตอบ:


15

\จะต้องใช้เพื่อป้องกันไม่ให้เกิดการขยายตัวของ$$(ปัจจุบันกระบวนการ id) สำหรับการทดแทนแบบสามครั้งคุณต้องมี eval สองเท่าดังนั้นจึงควรหลีกเลี่ยงการขยายเพิ่มเติมที่ไม่ต้องการในแต่ละ eval:

#! /bin/bash
l0=value
l1=l0
l2=l1
l3=l2
l4=l3

echo $l0
eval echo \$$l1
eval eval echo \\$\$$l2
eval eval eval echo \\\\$\\$\$$l3
eval eval eval eval echo \\\\\\\\$\\\\$\\$\$$l4

กระนั้น: l3=l2; eval eval eval echo \\\$\\$\$$l353294ดังนั้นจึงไม่ใช่แบบแยกส่วน
Profpatsch

2
ทำไมฉันถึงทำอะไรeval $(eval echo \$$l2)ไม่ได้เหมือนกัน ฉันเกลียดการทุบตีมันไม่สมเหตุสมผลเลย
Profpatsch

@Profpatsch: เพิ่มตัวอย่างเพิ่มเติม
choroba

อ่ามันคือn² ฉันไม่อยากลองแม้แต่จะเข้าใจว่าทำไมมันเป็นเช่นนี้
Profpatsch

2
@Profpatsch: ง่าย ในแต่ละขั้นตอนจำนวนของแบ็กสแลชลดลงครึ่งหนึ่ง (ดังนั้นจึงเป็น2ⁿจริง)
choroba


6

หากว่าค่าของFOOเป็นชื่อตัวแปรที่ถูกต้อง (พูดBAR) eval \$$FOOแยกค่าของBARคำเป็นคำที่แยกกันถือว่าแต่ละคำเป็นรูปแบบสัญลักษณ์แทนและดำเนินการคำแรกของผลลัพธ์เป็นคำสั่งผ่านคำอื่น ๆ ที่เป็นข้อโต้แย้ง เครื่องหมายในด้านหน้าของเงินดอลลาร์ทำให้ได้รับการปฏิบัติอย่างแท้จริงดังนั้นการโต้แย้งผ่านไปในตัวเป็นสตริงอักขระสี่ตัวeval$BAR

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

หากคุณต้องการรักษาค่าของตัวแปรเป็นตัวแปรโปรดระวังการอ้างถึงสิ่งต่าง ๆ อย่างถูกต้อง มันง่ายกว่ามากถ้าคุณตั้งค่าผลลัพธ์เป็นตัวแปร:

eval "value=\${$FOO}"

1
ตั้งเป็นตัวแปรดังนั้นคำแรกที่ไม่ถูกใช้เป็นคำสั่ง? มนุษย์เหตุผลแบบนี้ยากที่จะเข้าใจ มันเป็นปัญหาทั่วไปที่ฉันดูเหมือนจะมีกับการทุบตี
Profpatsch

4

{ba,z}sh วิธีการแก้

{ba,z}shนี่คือฟังก์ชั่นที่ทำงานทั้งใน ฉันเชื่อว่ามันเป็นไปตาม POSIX

โดยไม่ต้องโกรธด้วยการอ้างอิงคุณสามารถใช้มันสำหรับหลายระดับของการอ้อมเช่น:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

หรือถ้าคุณมีระดับการอ้อมมากขึ้น (!?!) คุณสามารถใช้การวนซ้ำได้

มันเตือนเมื่อได้รับ:

  • อินพุตว่าง
  • ชื่อตัวแปรที่ไม่ได้ตั้งค่า

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
var_expand() {
  if [ -z "${1-}" ] || [ $# -ne 1 ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

Cross-shell solution ที่ใช้งานได้สำหรับฉัน หวังว่า "การขยายตัวแปรคู่ที่เข้ากันได้กับ POSIX" จะเปลี่ยนเส้นทางไปยังคำตอบนี้ในอนาคต
BlueDrink9

2

เช่นเดียวกับ${$foo}ไม่ได้ทำงานในสถานที่ของ${(P)foo}ในzsh, ${${$foo}}ไม่ไม่ คุณเพียงแค่ต้องระบุแต่ละระดับของการอ้อม:

$ foo=bar
$ bar=baz
$ baz=3
$ echo $foo
bar
$ echo ${(P)foo}
baz
$ echo ${(P)${(P)foo}}
3

แน่นอน${!${!foo}}ไม่ทำงานbashเพราะbashไม่อนุญาตให้มีการแทนที่ซ้อนกัน


ขอบคุณที่รู้ดี ความอัปยศที่นี่คือ zsh-only ดังนั้นคุณจึงไม่สามารถใส่มันลงในสคริปต์ได้ (ทุกคนมีทุบตี แต่ไม่ใช่วิธีอื่น ๆ )
Profpatsch

ฉันสงสัยว่าzshติดตั้งบ่อยกว่าที่คุณคิด มันอาจไม่ได้เชื่อมโยงกับสิ่งที่shชอบbashบ่อย ๆ (แต่ไม่เสมอไป) คือ แต่มันอาจยังสามารถใช้ได้กับเชลล์สคริปต์ คิดเหมือนคุณทำ Perl หรือ Python
chepner

2

ทำไมคุณต้องทำเช่นนั้น?

คุณสามารถทำได้หลายขั้นตอนเช่น:

eval "l1=\${$var}"
eval "l2=\${$l1}"
...

หรือใช้ฟังก์ชั่นเช่น:

deref() {
  if [ "$1" -le 0 ]; then
    eval "$3=\$2"
  else
    eval "deref $(($1 - 1)) \"\${$2}\" \"\$3\""
  fi
}

แล้ว:

$ a=b b=c c=d d=e e=blah
$ deref 3 a res; echo "$res"
d
$ deref 5 a res; echo "$res"
blah

FWIW ในzsh:

$ echo ${(P)${(P)${(P)${(P)a}}}}
blah

หืมใช้หลายขั้นตอนไม่ได้เกิดขึ้นกับข้าจนกระทั่งบัดนี้แปลก
Profpatsch

มันเห็นได้ชัดจากมุมมองของฉันทำไม @Profpatsch ต้องการที่จะทำเช่นนั้น เพราะมันเป็นวิธีที่ใช้งานง่ายที่สุดที่จะทำ เป็นเรื่องยากที่จะดำเนินการแทนในหลายทุบตีเป็นอีกสิ่งหนึ่ง
Nikos Alexandris

2

โซลูชัน POSIX

คุณสามารถใช้ฟังก์ชั่นนี้สำหรับการสำรวจทางอ้อมในหลาย ๆ ระดับเช่น:

$ a=b
$ b=c
$ c=d
$ echo $(var_expand $(var_expand $a)
d

หรือถ้าคุณมีระดับการอ้อมมากขึ้น (!?!) คุณสามารถใช้การวนซ้ำได้

มันเตือนเมื่อได้รับ:

  • อินพุตว่าง
  • มีมากกว่าหนึ่งอาร์กิวเมนต์
  • ชื่อตัวแปรที่ไม่ได้ตั้งค่า

# Expand the variable named by $1 into its value. Works in both {ba,z}sh
# eg: a=HOME $(var_expand $a) == /home/me
function var_expand {
  if [ "$#" -ne 1 ] || [ -z "${1-}" ]; then
    printf 'var_expand: expected one argument\n' >&2;
    return 1;
  fi
  eval printf '%s' "\"\${$1?}\""
}

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