ชื่อตัวแปรแบบไดนามิกใน Bash


159

ฉันสับสนเกี่ยวกับสคริปต์ทุบตี

ฉันมีรหัสต่อไปนี้:

function grep_search() {
    magic_way_to_define_magic_variable_$1=`ls | tail -1`
    echo $magic_variable_$1
}

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

ดังนั้นเพื่อแสดงสิ่งที่ฉันต้องการ:

$ ls | tail -1
stack-overflow.txt

$ grep_search() open_box
stack-overflow.txt

ดังนั้นฉันจะกำหนด / ประกาศ$magic_way_to_define_magic_variable_$1อย่างไรและฉันควรจะเรียกมันอย่างไรภายในสคริปต์?

ฉันได้พยายามeval, ${...}, \$${...}แต่ฉันยังคงสับสน


3
อย่า ใช้อาเรย์แบบเชื่อมโยงเพื่อแมปชื่อคำสั่งกับข้อมูล
chepner

3
VAR = a; VAL = 333; อ่าน "$ VAR" <<< "$ VAL"; echo "A = $ A"
Grigory K

คำตอบ:


150

ใช้อาเรย์แบบเชื่อมโยงโดยใช้ชื่อคำสั่งเป็นกุญแจ

# Requires bash 4, though
declare -A magic_variable=()

function grep_search() {
    magic_variable[$1]=$( ls | tail -1 )
    echo ${magic_variable[$1]}
}

หากคุณไม่สามารถใช้อาร์เรย์ที่เชื่อมโยงได้ (เช่นคุณต้องสนับสนุนbash3) คุณสามารถใช้declareเพื่อสร้างชื่อตัวแปรแบบไดนามิก:

declare "magic_variable_$1=$(ls | tail -1)"

และใช้การขยายพารามิเตอร์ทางอ้อมเพื่อเข้าถึงค่า

var="magic_variable_$1"
echo "${!var}"

ดู BashFAQ: ร้าย - การประเมินตัวแปร


5
@DeaDEnD -aประกาศอาร์เรย์ที่มีการทำดัชนีไม่ใช่อาร์เรย์ที่เชื่อมโยงกัน ยกเว้นว่าอาร์กิวเมนต์grep_searchเป็นตัวเลขก็จะถือว่าเป็นพารามิเตอร์ที่มีค่าตัวเลข (ซึ่งเป็นค่าเริ่มต้นที่ 0 ถ้าพารามิเตอร์ไม่ได้ตั้งค่า)
chepner

1
อืมมม ฉันใช้ทุบตีและประกาศไม่ได้แสดงรายการมันเป็นตัวเลือก4.2.45(2) declare: usage: declare [-afFirtx] [-p] [name[=value] ...]ดูเหมือนว่าจะทำงานอย่างถูกต้องอย่างไรก็ตาม
Fent

declare -hใน 4.2.45 (2) declare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]สำหรับผมที่แสดงให้เห็นว่า คุณอาจตรวจสอบอีกครั้งว่าคุณใช้งานจริง 4.x ไม่ใช่ 3.2
chepner

5
ทำไมไม่เพียงdeclare $varname="foo"?
Ben Davis

1
${!varname}ง่ายและเข้ากันได้อย่างกว้างขวางมาก
Brad Hein

227

ฉันกำลังมองหาวิธีที่ดีกว่าในการทำมันเมื่อเร็ว ๆ นี้ อาเรย์ที่เกี่ยวข้องฟังเหมือน overkill สำหรับฉัน ดูสิ่งที่ฉันพบ:

suffix=bzz
declare prefix_$suffix=mystr

... และจากนั้น ...

varname=prefix_$suffix
echo ${!varname}

หากต้องการประกาศค่าโกลบอลภายในฟังก์ชันสามารถใช้ "declare -g" ใน bash> = 4.2 ใน bash ก่อนหน้านี้สามารถใช้ "อ่านอย่างเดียว" แทน "ประกาศ" ตราบใดที่คุณไม่ต้องการเปลี่ยนค่าในภายหลัง อาจโอเคสำหรับการกำหนดค่าหรือสิ่งที่มี
Sam Watkins

7
ดีที่สุดในการใช้รูปแบบตัวแปรที่ห่อหุ้ม: prefix_${middle}_postfix(เช่นการจัดรูปแบบของคุณจะไม่ทำงานvarname=$prefix_suffix)
msciwoj

1
ฉันติดอยู่กับ bash 3 และไม่สามารถใช้อาเรย์แบบเชื่อมโยงได้ เช่นนี้เป็นการช่วยชีวิต $ {! ... } ไม่ใช่ google ง่าย ๆ ฉันคิดว่ามันแค่ขยายชื่อ var
Neil McGill

10
@NeilMcGill: ดู "man bash" gnu.org/software/bash/manual/html_node/… : รูปแบบพื้นฐานของการขยายพารามิเตอร์คือ $ {พารามิเตอร์} <... > หากอักขระตัวแรกของพารามิเตอร์เป็นเครื่องหมายอัศเจรีย์ (!) จะมีการแนะนำระดับการเปลี่ยนทิศทางของตัวแปร Bash ใช้ค่าของตัวแปรที่เกิดขึ้นจากพารามิเตอร์ที่เหลือเป็นชื่อของตัวแปร จากนั้นตัวแปรนี้จะถูกขยายและใช้ค่านั้นในการทดแทนที่เหลือแทนที่จะเป็นค่าพารามิเตอร์เอง
Yorik.sar

1
@syntaxerror: คุณสามารถกำหนดค่าได้มากเท่าที่คุณต้องการด้วยคำสั่ง "ประกาศ" ด้านบน
Yorik.sar

48

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

ในตัวอย่างต่อไปนี้ผมจะสมมติว่าi=37และที่คุณต้องการที่จะนามแฝงตัวแปรที่มีชื่อที่มีค่าเริ่มต้นเป็นvar_37lolilol

วิธีที่ 1 ใช้ตัวแปร“ ตัวชี้”

คุณสามารถเก็บชื่อของตัวแปรในตัวแปรทางอ้อมโดยไม่เหมือนกับตัวชี้ C ทุบตีแล้วมีไวยากรณ์สำหรับการอ่านตัวแปร aliased: ขยายถึงค่าของตัวแปรที่มีชื่อเป็นค่าของตัวแปร${!name} nameคุณสามารถคิดว่ามันเป็นการขยายตัวที่สองขั้นตอน: ${!name}ขยายไปซึ่งจะขยาย$var_37lolilol

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) printfbuiltin สามารถกำหนดผลลัพธ์ให้กับตัวแปรที่มีชื่อได้ ตรงกันข้ามกับโซลูชันก่อนหน้านี้ใช้งานได้ไม่ต้องใช้ความพยายามพิเศษในการหลบหนีสิ่งต่าง ๆ เพื่อป้องกันการแยกและอื่น ๆ

printf -v "$name" '%s' 'babibab'
echo "$var_37"  # outputs “babibab”

ข้อเสีย:

  • พกพาน้อยลง (แต่ก็ดี)

วิธีที่ 2 ใช้ตัวแปร“ อ้างอิง”

ตั้งแต่ Bash 4.3 (เปิดตัวในปี 2014) declarebuiltin มีตัวเลือก-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!

ที่มา:


1
นี่คือคำตอบที่ดีที่สุดโดยเฉพาะอย่างยิ่งตั้งแต่${!varname}เทคนิคต้องมี var varnameกลางสำหรับ
RichVel

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

18

ตัวอย่างด้านล่างส่งคืนค่า $ name_of_var

var=name_of_var
echo $(eval echo "\$$var")

4
การซ้อนสองechos ด้วยการทดแทนคำสั่ง (ซึ่งคิดถึงเครื่องหมายคำพูด) ไม่จำเป็น นอกจากนี้ตัวเลือกที่ควรจะได้รับการ-n echoและเช่นเคยevalไม่ปลอดภัย ${!var}แต่ทั้งหมดนี้ไม่จำเป็นตั้งแต่ทุบตีมีความปลอดภัยมากขึ้นไวยากรณ์ที่ชัดเจนและสั้นเพื่อวัตถุประสงค์อย่างนี้:
Maëlan

4

สิ่งนี้น่าจะใช้ได้:

function grep_search() {
    declare magic_variable_$1="$(ls | tail -1)"
    echo "$(tmpvar=magic_variable_$1 && echo ${!tmpvar})"
}
grep_search var  # calling grep_search with argument "var"

4

มันจะใช้ได้เช่นกัน

my_country_code="green"
x="country"

eval z='$'my_"$x"_code
echo $z                 ## o/p: green

ในกรณีของคุณ

eval final_val='$'magic_way_to_define_magic_variable_"$1"
echo $final_val


3

ใช้ declare

ไม่จำเป็นต้องใช้คำนำหน้าเหมือนในคำตอบอื่น ๆ ทั้งอาร์เรย์ ใช้เพียงdeclare, ราคาคู่และการขยายตัวพารามิเตอร์

ฉันมักจะใช้เคล็ดลับต่อไปนี้เพื่อแยกรายการone to nอาร์กิวเมนต์ที่มีข้อโต้แย้งที่จัดรูปแบบเป็นkey=value otherkey=othervalue etc=etcLike:

# brace expansion just to exemplify
for variable in {one=foo,two=bar,ninja=tip}
do
  declare "${variable%=*}=${variable#*=}"
done
echo $one $two $ninja 
# foo bar tip

แต่การขยายรายการ argv เช่น

for v in "$@"; do declare "${v%=*}=${v#*=}"; done

เคล็ดลับพิเศษ

# parse argv's leading key=value parameters
for v in "$@"; do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
done
# consume argv's leading key=value parameters
while (( $# )); do
  case "$v" in ?*=?*) declare "${v%=*}=${v#*=}";; *) break;; esac
  shift
done

1
ดูเหมือนว่าวิธีการแก้ปัญหาที่สะอาดมาก ไม่มี bibs และ bobs ที่ชั่วร้ายและคุณใช้เครื่องมือที่เกี่ยวข้องกับตัวแปรไม่บดบังฟังก์ชั่นที่ไม่เกี่ยวข้องหรืออันตรายเช่นprintfหรือeval
kvantour

2

ว้าวไวยากรณ์ส่วนใหญ่แย่มาก! นี่คือทางออกหนึ่งที่มีไวยากรณ์ที่ง่ายกว่าถ้าคุณต้องการอ้างอิงอาร์เรย์ทางอ้อม:

#!/bin/bash

foo_1=("fff" "ddd") ;
foo_2=("ggg" "ccc") ;

for i in 1 2 ;
do
    eval mine=( \${foo_$i[@]} ) ;
    echo ${mine[@]} ;
done ;

สำหรับกรณีการใช้งานง่ายผมขอแนะนำให้ไวยากรณ์ที่อธิบายไว้ในขั้นสูงทุบตี Scripting คู่มือ


2
ABS เป็นคนที่มีชื่อเสียงในการแสดงการปฏิบัติที่ไม่ดีในตัวอย่างของมัน โปรดพิจารณาใช้พิงbash-hackers wikiหรือWooledge wikiซึ่งมีรายการBashFAQ # 6โดยตรงในหัวข้อแทน
ชาร์ลส์ดัฟฟี่

2
สิ่งนี้ใช้ได้เฉพาะในกรณีที่รายการเข้าfoo_1และfoo_2ไม่มีช่องว่างและสัญลักษณ์พิเศษ ตัวอย่างสำหรับรายการที่มีปัญหา: จะสร้างสองรายการภายใน'a b' จะไม่สร้างภายในรายการ จะขยายไปยังเนื้อหาของไดเรกทอรีทำงาน คุณสามารถป้องกันปัญหาเหล่านี้ได้ด้วยการอ้างถึง:mine''mine'*'eval 'mine=( "${foo_'"$i"'[@]}" )'
Socowi

@Socowi นั่นเป็นปัญหาทั่วไปที่มีการวนลูปผ่านอาร์เรย์ใด ๆ ใน BASH สิ่งนี้สามารถแก้ไขได้ด้วยการเปลี่ยน IFS ชั่วคราว (และแน่นอนว่าต้องเปลี่ยนกลับไป) มันเป็นการดีที่จะเห็นการทำงานของข้อความ
ingyhere

@ingy ที่ฉันขอแตกต่างกัน มันเป็นไม่ได้เป็นปัญหาทั่วไป มีวิธีแก้ไขปัญหามาตรฐาน: อ้างคำพูด[@]สร้าง จะขยายไปยังรายชื่อที่ถูกต้องของรายการได้โดยไม่มีปัญหาเช่นแยกคำหรือการขยายตัวของ"${array[@]}" *นอกจากนี้ปัญหาการแยกคำสามารถหลีกเลี่ยงได้IFSหากคุณรู้ว่าอักขระที่ไม่ใช่ค่าว่างใด ๆ ที่ไม่เคยปรากฏในอาร์เรย์ การรักษาที่แท้จริงนอกจากนี้การไม่สามารถทำได้โดยการตั้งค่า* IFSไม่ว่าคุณจะตั้งค่าIFS='*'และแยกที่ดาวหรือคุณตั้งค่าIFS=somethingOtherและ*ขยาย
Socowi

@Socowi คุณกำลังสมมติว่าการขยายตัวของเชลล์นั้นไม่เป็นที่ต้องการและนั่นก็ไม่ได้เป็นเช่นนั้นเสมอไป นักพัฒนาบ่นถึงข้อบกพร่องเมื่อนิพจน์ของเชลล์ไม่ขยายหลังจากการอ้างอิงทุกสิ่ง ทางออกที่ดีคือการรู้จักข้อมูลและสร้างสคริปต์อย่างเหมาะสมแม้ใช้|หรือLFเป็น IFS อีกครั้งปัญหาทั่วไปในลูปคือโทเค็นที่เกิดขึ้นโดยค่าเริ่มต้นเพื่อให้การอ้างอิงเป็นโซลูชั่นพิเศษเพื่อให้สายอักขระเพิ่มเติมที่มีโทเค็น (อาจเป็นการขยายตัวแบบพารามิเตอร์ / พารามิเตอร์แบบขยายหรืออ้างถึงสตริงส่วนขยาย แต่ไม่ใช่ทั้งคู่) หากจำเป็นต้องใช้ 8 อัญประกาศเพื่ออ่าน var เชลล์เป็นภาษาที่ไม่ถูกต้อง
ingyhere

1

สำหรับอาร์เรย์ที่มีการจัดทำดัชนีคุณสามารถอ้างอิงได้ดังนี้:

foo=(a b c)
bar=(d e f)

for arr_var in 'foo' 'bar'; do
    declare -a 'arr=("${'"$arr_var"'[@]}")'
    # do something with $arr
    echo "\$$arr_var contains:"
    for char in "${arr[@]}"; do
        echo "$char"
    done
done

เชื่อมโยงอาร์เรย์สามารถอ้างอิงในทำนองเดียวกัน แต่ต้องมี-Aสวิทช์แทนdeclare-a


1

วิธีพิเศษที่ไม่พึ่งพาซึ่งเปลือก / envsubstรุ่นทุบตีคุณมีคือการใช้ ตัวอย่างเช่น:

newvar=$(echo '$magic_variable_'"${dynamic_part}" | envsubst)

0

ฉันต้องการที่จะสามารถสร้างชื่อตัวแปรที่มีอาร์กิวเมนต์แรกของคำสั่ง

script.sh ไฟล์:

#!/usr/bin/env bash
function grep_search() {
  eval $1=$(ls | tail -1)
}

ทดสอบ:

$ source script.sh
$ grep_search open_box
$ echo $open_box
script.sh

ตามhelp eval:

เรียกใช้อาร์กิวเมนต์เป็นคำสั่งเชลล์


นอกจากนี้คุณยังสามารถใช้ Bash ${!var}อ้อมขยายดังกล่าวแล้ว แต่มันไม่สนับสนุนการดึงดัชนีอาร์เรย์


สำหรับการอ่านเพิ่มเติมหรือตัวอย่างการตรวจสอบBashFAQ / 006 เกี่ยวกับความร้าย

เราไม่ทราบถึงเคล็ดลับใด ๆ ที่สามารถทำซ้ำการทำงานนั้นใน POSIX หรือ Bourne shell ที่ไม่มีevalซึ่งอาจทำได้ยาก ดังนั้นพิจารณาการใช้นี้ที่สับเสี่ยงของคุณเอง

อย่างไรก็ตามคุณควรพิจารณาใช้ทางอ้อมอีกครั้งตามหมายเหตุต่อไปนี้

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

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


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