นอกเหนือจากอาร์เรย์ที่เชื่อมโยงแล้วมีหลายวิธีในการบรรลุถึงตัวแปรแบบไดนามิกใน Bash โปรดทราบว่าเทคนิคเหล่านี้ทั้งหมดมีความเสี่ยงซึ่งจะกล่าวถึงในตอนท้ายของคำตอบนี้
ในตัวอย่างต่อไปนี้ผมจะสมมติว่าi=37
และที่คุณต้องการที่จะนามแฝงตัวแปรที่มีชื่อที่มีค่าเริ่มต้นเป็นvar_37
lolilol
วิธีที่ 1 ใช้ตัวแปร“ ตัวชี้”
คุณสามารถเก็บชื่อของตัวแปรในตัวแปรทางอ้อมโดยไม่เหมือนกับตัวชี้ C ทุบตีแล้วมีไวยากรณ์สำหรับการอ่านตัวแปร aliased: ขยายถึงค่าของตัวแปรที่มีชื่อเป็นค่าของตัวแปร${!name}
name
คุณสามารถคิดว่ามันเป็นการขยายตัวที่สองขั้นตอน: ${!name}
ขยายไปซึ่งจะขยาย$var_37
lolilol
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
น่าเสียดายที่ไม่มีไวยากรณ์รองสำหรับการแก้ไขตัวแปร aliased แต่คุณสามารถทำงานที่ได้รับมอบหมายด้วยหนึ่งในเทคนิคต่อไปนี้
1a การมอบหมายด้วยeval
eval
เป็นสิ่งที่ชั่วร้าย แต่ก็เป็นวิธีที่ง่ายและสะดวกที่สุดในการบรรลุเป้าหมายของเรา คุณจะต้องระมัดระวังหลบหนีด้านขวามือของงานตามที่มันจะได้รับการประเมินครั้งที่สอง วิธีที่ง่ายและเป็นระบบในการทำเช่นนี้คือการประเมินทางด้านขวาล่วงหน้า (หรือใช้printf %q
)
และคุณควรตรวจสอบด้วยตนเองว่าด้านซ้ายเป็นชื่อตัวแปรที่ถูกต้องหรือชื่อที่มีดัชนี (จะเกิดevil_code #
อะไรขึ้น?) ในทางตรงกันข้ามวิธีการอื่นทั้งหมดด้านล่างบังคับใช้โดยอัตโนมัติ
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
ข้อเสีย:
- ไม่ตรวจสอบความถูกต้องของชื่อตัวแปร
eval
คือความชั่วร้าย
eval
คือความชั่วร้าย
eval
คือความชั่วร้าย
1b การมอบหมายด้วยread
read
ในตัวช่วยให้คุณสามารถกำหนดค่าให้กับตัวแปรที่คุณให้ชื่อที่เป็นความจริงที่สามารถนำมาใช้ประโยชน์ในการร่วมกับที่นี่สตริง:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
IFS
ส่วนหนึ่งและตัวเลือกที่-r
ตรวจสอบให้แน่ใจว่าค่าที่ได้รับมอบหมายตามที่เป็นในขณะที่ตัวเลือกที่-d ''
จะช่วยให้การกำหนดค่าหลายคู่สาย เนื่องจากตัวเลือกสุดท้ายนี้คำสั่งจะส่งคืนพร้อมโค้ดออกที่ไม่ใช่ศูนย์
โปรดทราบว่าเนื่องจากเราใช้สตริงที่นี่อักขระขึ้นบรรทัดใหม่จะถูกผนวกเข้ากับค่า
ข้อเสีย:
- ค่อนข้างคลุมเครือ
- ส่งคืนด้วยโค้ดออกที่ไม่ใช่ศูนย์
- เพิ่มบรรทัดใหม่ต่อท้ายค่า
1c การมอบหมายด้วยprintf
ตั้งแต่ Bash 3.1 (เปิดตัวในปี 2005) printf
builtin สามารถกำหนดผลลัพธ์ให้กับตัวแปรที่มีชื่อได้ ตรงกันข้ามกับโซลูชันก่อนหน้านี้ใช้งานได้ไม่ต้องใช้ความพยายามพิเศษในการหลบหนีสิ่งต่าง ๆ เพื่อป้องกันการแยกและอื่น ๆ
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
ข้อเสีย:
วิธีที่ 2 ใช้ตัวแปร“ อ้างอิง”
ตั้งแต่ Bash 4.3 (เปิดตัวในปี 2014) declare
builtin มีตัวเลือก-n
สำหรับสร้างตัวแปรซึ่งเป็น“ การอ้างอิงชื่อ” ไปยังตัวแปรอื่นซึ่งเหมือนกับการอ้างอิง C ++ เช่นเดียวกับในวิธีที่ 1 การอ้างอิงจะเก็บชื่อของตัวแปร aliased แต่ทุกครั้งที่มีการเข้าถึงการอ้างอิง (ไม่ว่าจะเป็นการอ่านหรือการกำหนด) Bash จะแก้ไขทิศทางโดยอัตโนมัติ
นอกจากนี้ Bash ยังมีรูปแบบพิเศษและสับสนมากสำหรับการรับค่าของตัวอ้างอิงเอง, ตัดสินด้วยตัวเอง: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
สิ่งนี้ไม่ได้หลีกเลี่ยงข้อผิดพลาดที่อธิบายไว้ด้านล่าง แต่อย่างน้อยก็ทำให้ไวยากรณ์ตรงไปตรงมา
ข้อเสีย:
ความเสี่ยง
เทคนิคการใช้นามแฝงเหล่านี้มีความเสี่ยงหลายประการ หนึ่งคือการดำเนินการรหัสที่กำหนดในแต่ละครั้งที่คุณแก้ไขตรงนี้ (อย่างใดอย่างหนึ่งสำหรับการอ่านหรือสำหรับการกำหนด) แน่นอนแทนที่จะเป็นชื่อตัวแปรสเกลาร์เช่นvar_37
คุณอาจใช้นามแฝงตัวห้อยของอาร์เรย์เช่นarr[42]
กัน แต่ทุบตีประเมินเนื้อหาของวงเล็บในแต่ละครั้งมันเป็นสิ่งจำเป็นเพื่อขจัดรอยหยักarr[$(do_evil)]
จะมีผลที่ไม่คาดคิด ... เป็นผลให้เพียงใช้เทคนิคเหล่านี้เมื่อคุณควบคุมการมาของนามแฝง
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
ความเสี่ยงที่สองคือการสร้างนามแฝงแบบวน เนื่องจากตัวแปร Bash ถูกระบุด้วยชื่อและไม่ได้อยู่ในขอบเขตของตัวแปรคุณอาจสร้างนามแฝงให้กับตัวเองโดยไม่ตั้งใจ (ในขณะที่คิดว่าจะใช้นามแฝงตัวแปรจากขอบเขตล้อมรอบ) สิ่งนี้อาจเกิดขึ้นโดยเฉพาะเมื่อใช้ชื่อตัวแปรทั่วไป (เช่นvar
) เป็นผลให้เพียงใช้เทคนิคเหล่านี้เมื่อคุณควบคุมชื่อของตัวแปร aliased
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
ที่มา: