วิธีรับอาร์กิวเมนต์สุดท้ายไปยังฟังก์ชัน / bin / sh


11

เป็นวิธีที่ดีกว่าในการใช้งานprint_last_argอะไร

#!/bin/sh

print_last_arg () {
    eval "echo \${$#}"  # this hurts
}

print_last_arg foo bar baz
# baz

(ถ้าเป็นแบบนี้ให้บอกว่า#!/usr/bin/zshแทนที่จะ#!/bin/shรู้ว่าจะทำอย่างไรปัญหาของฉันคือการหาวิธีที่เหมาะสมในการใช้สิ่งนี้เพื่อ#!/bin/sh)

แก้ไข: ข้างต้นเป็นเพียงตัวอย่างที่โง่ เป้าหมายของฉันคือไม่พิมพ์อาร์กิวเมนต์สุดท้าย แต่มีวิธีอ้างถึงอาร์กิวเมนต์ล่าสุดภายในฟังก์ชันเชลล์


EDIT2: ฉันขอโทษสำหรับคำถามที่มีคำพูดที่ไม่ชัดเจน ฉันหวังว่าจะทำให้ถูกต้องในครั้งนี้

หากนี่เป็น/bin/zshแทนที่จะ/bin/shฉันสามารถเขียนสิ่งนี้

#!/bin/zsh

print_last_arg () {
    local last_arg=$argv[$#]
    echo $last_arg
}

การแสดงออก$argv[$#]เป็นตัวอย่างของสิ่งที่ผมอธิบายไว้ในแก้ไขครั้งแรกของฉันในฐานะที่เป็นวิธีการที่จะอ้างถึงอาร์กิวเมนต์สุดท้ายภายในฟังก์ชั่นเชลล์

ดังนั้นฉันควรเขียนตัวอย่างดั้งเดิมของฉันเช่นนี้:

print_last_arg () {
    local last_arg=$(eval "echo \${$#}")   # but this hurts even more
    echo $last_arg
}

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

แต่โปรดทราบว่าในตัวอย่างทั้งหมดอาร์กิวเมนต์สุดท้ายมีการเข้าถึงที่ไม่ทำลาย IOW การเข้าถึงอาร์กิวเมนต์ล่าสุดทำให้อาร์กิวเมนต์ตำแหน่งเป็นการรวมที่ไม่ได้รับผลกระทบทั้งหมด


unix.stackexchange.com/q/145522/117549 ให้คะแนนความเป็นไปได้ที่หลากหลายของ #! / bin / sh - คุณสามารถ จำกัด ให้แคบลงได้ไหม?
Jeff Schaller

ฉันชอบการแก้ไข แต่บางทีคุณอาจสังเกตเห็นด้วยว่ามีคำตอบที่นี่ซึ่งเสนอวิธีการที่ไม่ทำลายการอ้างอิงอาร์กิวเมนต์สุดท้าย ... ? คุณไม่ควรถือเอาvar=$( eval echo \${$#})ไปeval var=\${$#}- ทั้งสองมีอะไรเหมือนกัน
mikeserv

1
ไม่แน่ใจว่าฉันได้รับบันทึกย่อสุดท้ายของคุณ แต่คำตอบเกือบทั้งหมดที่ให้มานั้นยังไม่เป็นอันตรายในแง่ที่ว่าพวกเขารักษาอาร์กิวเมนต์ของสคริปต์ที่ทำงานอยู่ วิธีการแก้ปัญหาเท่านั้นshiftและset -- ...ตามอาจทำลายล้างเว้นแต่ใช้ในฟังก์ชั่นที่พวกเขาจะไม่เป็นอันตรายเช่นกัน
jlliagre

@jlliagre - แต่พวกเขายังคงทำลายล้างในหลัก - พวกเขาต้องการสร้างบริบทที่ใช้แล้วทิ้งเพื่อให้พวกเขาสามารถทำลายเพื่อค้นพบ แต่ ... หากคุณได้รับบริบทที่สองอยู่แล้ว - ทำไมไม่เพียงแค่ได้บริบทที่คุณสามารถสร้างดัชนีได้? มีอะไรผิดปกติในการใช้เครื่องมือที่มีไว้สำหรับงานหรือไม่ การตีความการขยายตัวของเชลล์เนื่องจากอินพุตที่ขยายได้นั้นเป็นงานของ eval และไม่มีอะไรแตกต่างกันอย่างมีนัยสำคัญเกี่ยวกับการทำeval "var=\${$#}"เมื่อเทียบกับvar=${arr[evaled index]}ยกเว้นว่า$#เป็นค่าที่ปลอดภัยรับประกัน ทำไมคัดลอกทั้งชุดแล้วทำลายมันเมื่อคุณสามารถสร้างดัชนีได้โดยตรง
mikeserv

1
@mikeserv A สำหรับลูปที่ทำในส่วนหลักของเชลล์จะทำให้อาร์กิวเมนต์ทั้งหมดไม่เปลี่ยนแปลง ฉันเห็นพ้องกันว่าการวนอาร์กิวเมนต์ทั้งหมดนั้นไม่ได้รับการปรับให้เหมาะสมโดยเฉพาะอย่างยิ่งหากมีหลายพันข้อถูกส่งไปยังเชลล์และฉันก็เห็นด้วยเช่นกันว่าการเข้าถึงอาร์กิวเมนต์สุดท้ายโดยตรงกับดัชนีที่เหมาะสมนั้นเป็นคำตอบที่ดีที่สุด ) แต่นอกเหนือจากนั้นไม่มีอะไรที่เป็นอันตรายและไม่มีการสร้างบริบทเพิ่มเติม
jlliagre

คำตอบ:


1
eval printf %s${1+"'\n' \"the last arg is \${$#"\}\"}

... จะพิมพ์สตริงthe last arg isตามด้วย<space>ค่าของอาร์กิวเมนต์สุดท้ายและ<newline>ต่อท้ายหากมีอาร์กิวเมนต์อย่างน้อย 1 ตัวหรืออย่างอื่นสำหรับอาร์กิวเมนต์ศูนย์จะไม่พิมพ์เลย

ถ้าคุณทำ:

eval ${1+"lastarg=\${$#"\}}

... จากนั้นคุณจะกำหนดค่าของอาร์กิวเมนต์สุดท้ายให้กับตัวแปร shell $lastargหากมีอาร์กิวเมนต์อย่างน้อย 1 ตัวหรือคุณไม่ต้องทำอะไรเลย ไม่ว่าจะด้วยวิธีใดคุณก็ทำได้อย่างปลอดภัยและควรพกพาได้แม้กับเจ้า Olde Bourne shell ฉันคิดว่า

นี่คืออีกอันที่จะทำงานคล้ายกันแม้ว่ามันจะต้องคัดลอกอาเรย์ทั้งหมดทั้งสองครั้ง(และจำเป็นต้องมีprintfใน$PATHสำหรับเชลล์เป้าหมาย) :

if   [ "${1+:}" ]
then set "$#" "$@" "$@"
     shift    "$1"
     printf %s\\n "$1"
     shift
fi

ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
terdon

2
FYI ข้อเสนอแนะแรกของคุณล้มเหลวภายใต้เชลล์ bourne เดิมพร้อมข้อผิดพลาด "การแทนที่ไม่ดี" หากไม่มีข้อโต้แย้งปรากฏขึ้น ทั้งที่เหลืออยู่ทำงานตามที่คาดไว้
jlliagre

@jillagre - ขอบคุณ ฉันไม่แน่ใจเกี่ยวกับหนึ่งในนั้น - แต่ค่อนข้างแน่ใจในอีกสอง มันเป็นเพียงความตั้งใจที่จะแสดงให้เห็นถึงวิธีการที่อาจเข้าถึงข้อโต้แย้งโดยการอ้างอิงแบบอินไลน์ ฟังก์ชั่นควรเปิดพร้อมกับบางสิ่งบางอย่าง${1+":"} returnทันที - เพราะใครต้องการให้มันทำอะไรหรือเสี่ยงต่อผลข้างเคียงใด ๆ หากถูกเรียกเพื่ออะไร คณิตศาสตร์นั้นค่อนข้างสวย - ถ้าคุณมั่นใจได้ว่าคุณสามารถขยายจำนวนเต็มบวกได้evalตลอดทั้งวัน $OPTINDดีสำหรับมัน
mikeserv

3

นี่เป็นวิธีที่เรียบง่าย:

print_last_arg () {
  if [ "$#" -gt 0 ]
  then
    s=$(( $# - 1 ))
  else
    s=0
  fi
  shift "$s"
  echo "$1"
}

(อัปเดตตามจุดของ @ cuonglm ที่ต้นฉบับล้มเหลวเมื่อไม่ผ่านการขัดแย้งตอนนี้สะท้อนเส้นว่าง - เปลี่ยนพฤติกรรมนั้นในelseข้อถ้าต้องการ)


จำเป็นต้องใช้ / usr / xpg4 / bin / sh บน Solaris; แจ้งให้เราทราบหาก Solaris #! / bin / sh เป็นข้อกำหนด
Jeff Schaller

คำถามนี้ไม่เกี่ยวกับสคริปต์เฉพาะที่ฉันพยายามเขียน แต่เป็นวิธีการทั่วไป ฉันกำลังมองหาสูตรที่ดีสำหรับสิ่งนี้ แน่นอนยิ่งพกพาได้มากเท่าไหร่ก็ยิ่งดีเท่านั้น
kjo

คณิตศาสตร์ $ (()) ล้มเหลวใน Solaris / bin / sh; ใช้สิ่งที่ต้องการ: shift `expr $ # - 1` หากจำเป็น
Jeff Schaller

อีกทางเลือกหนึ่งสำหรับ Solaris / bin / shecho "$# - 1" | bc
Jeff Schaller

1
นั่นคือสิ่งที่ฉันต้องการจะเขียน แต่มันล้มเหลวในเชลล์ที่ 2 ที่ฉันลอง - NetBSD ซึ่งเห็นได้ชัดว่าเป็นเชลล์ Almquist ที่ไม่สนับสนุน :(
Jeff Schaller

3

รับตัวอย่างของการโพสต์เปิด (อาร์กิวเมนต์ตำแหน่งโดยไม่มีช่องว่าง):

print_last_arg foo bar baz

สำหรับค่าเริ่มต้นIFS=' \t\n'วิธีการเกี่ยวกับ:

args="$*" && printf '%s\n' "${args##* }"

สำหรับการขยายที่ปลอดภัยยิ่งขึ้น"$*"ให้ตั้งค่า IFS (ต่อ @ StéphaneChazelas):

( IFS=' ' args="$*" && printf '%s\n' "${args##* }" )

แต่ข้างต้นจะล้มเหลวถ้าอาร์กิวเมนต์ตำแหน่งของคุณสามารถมีช่องว่าง ในกรณีนี้ให้ใช้สิ่งนี้แทน:

for a in "$@"; do : ; done && printf '%s\n' "$a"

โปรดทราบว่าเทคนิคเหล่านี้หลีกเลี่ยงการใช้evalและไม่มีผลข้างเคียง

ทดสอบที่shellcheck.net


1
คนแรกล้มเหลวถ้าอาร์กิวเมนต์สุดท้ายมีช่องว่าง
cuonglm

1
โปรดทราบว่าคนแรกยังไม่ทำงานในเปลือกก่อน POSIX
cuonglm

@cuonglm คุณสังเกตเห็นถูกต้องแล้วตอนนี้
AsymLabs

นอกจากนี้ยังถือว่าอักขระตัวแรกของ$IFSคือช่องว่าง
Stéphane Chazelas

1
มันจะเป็นถ้าไม่มี $ IFS ในสภาพแวดล้อมที่ไม่ได้ระบุเป็นอย่างอื่น แต่เนื่องจากคุณต้องตั้งค่า IFS แทบทุกครั้งที่คุณใช้ตัวแยก + + glob (ปล่อยให้ส่วนขยายไม่มีการอ้างอิง) วิธีหนึ่งในการจัดการกับ IFS คือตั้งค่าเมื่อใดก็ตามที่คุณต้องการ มันจะไม่เป็นอันตรายต่อการมีที่นี่เพียงที่จะทำให้มันชัดเจนว่ามันถูกใช้สำหรับการขยายตัวของIFS=' ' "$*"
Stéphane Chazelas

3

แม้ว่าคำถามนี้จะมีอายุเพียง 2 ปี แต่ฉันคิดว่าฉันจะแบ่งปันตัวเลือกการรวบรวมค่อนข้าง

print_last_arg () {
    echo "${@:${#@}:${#@}}"
}

มาวิ่งกันเถอะ

print_last_arg foo bar baz
baz

ทุบตีการขยายตัวพารามิเตอร์เปลือก

แก้ไข

รัดกุมยิ่งขึ้น: echo "${@: -1}"

(ระวังพื้นที่)

แหล่ง

ทดสอบบน macOS 10.12.6 แล้วก็ควรคืนอาร์กิวเมนต์สุดท้ายให้กับรสชาติที่มีอยู่ * ส่วนใหญ่ ...

เจ็บน้อยกว่ามาก ¯\_(ツ)_/¯


นี่ควรเป็นคำตอบที่ยอมรับได้ ยิ่งไปกว่านั้นคือ: echo "${*: -1}"ซึ่งshellcheckจะไม่บ่นเกี่ยวกับ
Tom Hale

4
สิ่งนี้จะไม่ทำงานกับ POSIX sh ธรรมดา${array:n:m:}เป็นส่วนเสริม (คำถามที่ได้กล่าวถึงอย่างชัดเจน/bin/sh)
ilkkachu

2

POSIXly:

while [ "$#" -gt 1 ]; do
  shift
done

printf '%s\n' "$1"

(วิธีนี้ใช้ได้กับเชลล์เป้าหมายเดิมด้วย)

ด้วยเครื่องมือมาตรฐานอื่น ๆ :

awk 'BEGIN{print ARGV[ARGC-1]}' "$@"

(สิ่งนี้จะไม่ทำงานกับเก่าawkซึ่งไม่มีARGV)


ในกรณีที่ยังคงมีเปลือกบอร์นawkนอกจากนี้ยังอาจจะเป็น awk ARGVเก่าที่ไม่ได้มี
Stéphane Chazelas

@ StéphaneChazelas: เห็นด้วย อย่างน้อยก็ทำงานได้กับเวอร์ชั่นของ Brian Kernighan คุณมีอุดมคติหรือไม่?
cuonglm

วิธีนี้ลบอาร์กิวเมนต์ วิธีการเก็บพวกเขา?

@BinaryZebra: ใช้awkวิธีการหรือใช้forวงธรรมดาอื่น ๆ เช่นอื่นได้หรือผ่านพวกเขาไปยังฟังก์ชั่น
cuonglm

2

สิ่งนี้ควรทำงานกับเชลล์ที่เข้ากันได้กับ POSIX และจะทำงานกับเชลล์ Solaris Bourne รุ่นก่อน POSIX:

do=;for do do :;done;printf "%s\n" "$do"

และนี่คือฟังก์ชั่นตามวิธีการเดียวกัน:

print_last_arg()
  if [ "$*" ]; then
    for do do :;done
    printf "%s\n" "$do"
  else
    echo
  fi

PS: อย่าบอกฉันว่าฉันลืมวงเล็บปีกกาไว้รอบตัวฟังก์ชัน ;-)


2
คุณลืมวงเล็บปีกการอบ ๆ ร่างกายฟังก์ชัน ;-)

@BinaryZebra ฉันเตือนคุณ ;-) ฉันไม่ได้ลืมพวกเขา เครื่องมือจัดฟันเป็นทางเลือกที่น่าแปลกใจที่นี่
jlliagre

1
@jlliagre ฉันถูกเตือนแน่นอน ;-): P ...... และ: แน่นอน!

ส่วนไหนของข้อมูลจำเพาะไวยากรณ์ที่อนุญาตให้ใช้
Tom Hale

@TomHale กฎไวยากรณ์เชลล์อนุญาตให้ทั้งคู่หายไปin ...ในforลูปและไม่มีเครื่องหมายปีกการอบifคำสั่งที่ใช้เป็นเนื้อหาของฟังก์ชัน ดูfor_clauseและcompound_commandในpubs.opengroup.org/onlinepubs/9699919799
jlliagre

2

จาก"Unix - คำถามที่พบบ่อย"

(1)

unset last
if    [ $# -gt 0 ]
then  eval last=\${$#}
fi
echo  "$last"

ถ้าจำนวนของการขัดแย้งที่อาจจะศูนย์แล้วอาร์กิวเมนต์ศูนย์$0(ปกติชื่อของสคริปต์) $lastจะถูกกำหนดให้ นั่นเป็นเหตุผลสำหรับถ้า

(2)

unset last
for   last
do    :
done
echo  "$last"

(3)

for     i
do
        third_last=$second_last
        second_last=$last
        last=$i
done
echo    "$last"

เพื่อหลีกเลี่ยงการพิมพ์บรรทัดว่างเมื่อไม่มีข้อโต้แย้งให้แทนที่echo "$last"สำหรับ:

${last+false} || echo "${last}"

if [ $# -gt 0 ]นับเป็นศูนย์การโต้แย้งจะหลีกเลี่ยงได้โดย

นี่ไม่ใช่สำเนาที่แน่นอนของสิ่งที่อยู่ในลิงค์ในหน้าการปรับปรุงบางอย่างถูกเพิ่มเข้ามา


0

สิ่งนี้จะทำงานได้กับทุกระบบที่perlติดตั้ง (ดังนั้น UNICE ส่วนใหญ่):

print_last_arg () {
    printf '%s\0' "$@" | perl -0ne 's/\0//;$l=$_;}{print "$l\n"'
}

เคล็ดลับคือการใช้งานprintfที่จะเพิ่ม\0หลังจากที่แต่ละอาร์กิวเมนต์เปลือกแล้วperlของ-0สวิทช์ซึ่งชุดคั่นบันทึกที่จะเป็นโมฆะ จากนั้นเราก็ย้ำกว่าการป้อนข้อมูลที่ลบและบันทึกแต่ละบรรทัดโมฆะเป็นสิ้นสุด\0 บล็อก (ว่าอะไรเป็น) จะดำเนินการหลังจากเข้าทั้งหมดได้รับการอ่านเพื่อจะพิมพ์ "เส้น" ครั้งสุดท้ายเมื่อ: อาร์กิวเมนต์เปลือกที่ผ่านมา$lEND}{


@ mikeserv อาจริงฉันไม่ได้ทดสอบกับ newline ใด ๆ ยกเว้นอาร์กิวเมนต์สุดท้าย รุ่นที่แก้ไขควรทำงานเพื่ออะไรก็ได้ แต่อาจซับซ้อนเกินไปสำหรับงานง่าย ๆ
terdon

ใช่เรียกการปฏิบัติการภายนอก(ถ้ายังไม่ได้เป็นส่วนหนึ่งของตรรกะ)คือสำหรับฉันมือหนัก ๆ แต่ถ้าจุดคือการผ่านการโต้แย้งว่าไปsed, awkหรือperlแล้วมันอาจจะเป็นข้อมูลที่มีค่าอยู่แล้ว ยังคุณสามารถทำเช่นเดียวกันกับsed -zรุ่น GNU ที่ทันสมัย
mikeserv

ทำไมคุณถึงถูกลดระดับลง? นี่เป็นสิ่งที่ดี ... ฉันคิดว่า?
mikeserv

0

นี่คือรุ่นที่ใช้การเรียกซ้ำ ไม่ทราบว่า POSIX ปฏิบัติตามสิ่งนี้อย่างไร ...

print_last_arg()
{
    if [ $# -gt 1 ] ; then
        shift
        echo $( print_last_arg "$@" )
    else
        echo "$1"
    fi
}

0

แนวความคิดง่ายคณิตศาสตร์ไม่มีห่วงไม่มีEVALฟังก์ชั่นเพียง
จำไว้ว่า Bourne shell ไม่มีเลขคณิต (จำเป็นต้องใช้ภายนอกexpr) หากต้องการได้รับแบบฟรีทางคณิตศาสตร์evalฟรีตัวเลือกนี้เป็นตัวเลือก ฟังก์ชั่นที่ต้องการหมายถึง SVR3 หรือสูงกว่า (ไม่มีการเขียนทับพารามิเตอร์)
ดูด้านล่างสำหรับรุ่นที่แข็งแกร่งกว่าด้วย printf

printlast(){
    shift "$1"
    echo "$1"
}

printlast "$#" "$@"          ### you may use ${1+"$@"} here to allow
                             ### a Bourne empty list of arguments,
                             ### but wait for a correct solution below.

โครงสร้างสายนี้printlastจะได้รับการแก้ไขข้อโต้แย้งจะต้องมีการตั้งอยู่ในรายการของอาร์กิวเมนต์เปลือก$1, $2ฯลฯ (กองอาร์กิวเมนต์) และโทรทำตามที่กำหนด

หากต้องการเปลี่ยนรายการอาร์กิวเมนต์ให้ตั้งค่าดังนี้

set -- o1e t2o t3r f4u
printlast "$#" "$@"

หรือสร้างฟังก์ชั่นการใช้งาน ( getlast) ที่ง่ายขึ้นซึ่งอาจอนุญาตให้เกิดข้อโต้แย้งทั่วไป (แต่ไม่เร็วเท่าข้อโต้แย้งจะถูกส่งผ่านสองครั้ง)

getlast(){ printlast "$#" "$@"; }
getlast o1e t2o t3r f4u

โปรดทราบว่าข้อโต้แย้ง (จากgetlastหรือทั้งหมดรวมอยู่ใน$@สิ่งพิมพ์) อาจมีช่องว่างบรรทัดใหม่ ฯลฯ แต่ไม่ใช่ NUL

ดีกว่า

รุ่นนี้ไม่ได้พิมพ์0เมื่อรายการอาร์กิวเมนต์ว่างเปล่าและใช้ printf ที่มีประสิทธิภาพมากขึ้น (ถอยกลับไปechoหากภายนอกprintfไม่พร้อมใช้งานสำหรับเชลล์เก่า)

printlast(){ shift  "$1"; printf '%s' "$1"; }
getlast  ()  if     [ $# -gt 0 ]
             then   printlast "$#" "$@"
                    echo    ### optional if a trailing newline is wanted.
             fi
### The {} braces were removed on purpose.

getlast 1 2 3 4    # will print 4

ใช้ EVAL

หาก Bourne shell นั้นเก่ากว่าและไม่มีฟังก์ชั่นหรือด้วยเหตุผลบางอย่างในการใช้ eval เป็นตัวเลือกเท่านั้น:

หากต้องการพิมพ์ค่า:

if    [ $# -gt 0 ]
then  eval printf "'1%s\n'" \"\$\{$#\}\"
fi

ในการตั้งค่าในตัวแปร:

if    [ $# -gt 0 ]
then  eval 'last_arg='\"\$\{$#\}\"
fi

หากจำเป็นต้องทำในฟังก์ชันอาร์กิวเมนต์ต้องถูกคัดลอกไปยังฟังก์ชัน:

print_last_arg () {
    local last_arg                ### local only works on more modern shells.
    eval 'last_arg='\"\$\{$#\}\"
    echo "last_arg3=$last_arg"
}

print_last_arg "$@"               ### Shell arguments are sent to the function.

มันดีกว่า แต่บอกว่าไม่ใช้ลูป - แต่ทำ สำเนาอาร์เรย์เป็นห่วง และด้วยฟังก์ชั่นสองอย่างมันใช้สองลูป ด้วย - มีเหตุผลบางอย่างที่ควรทำทั้งอาเรย์เพื่อสร้างดัชนีด้วยevalหรือไม่?
mikeserv

1
คุณเห็นอะไรบ้างfor loopหรือwhile loopเปล่า?


1
ตามมาตรฐานของคุณฉันเดาว่ารหัสทั้งหมดมีลูปแม้แต่ a echo "Hello"มีลูปเนื่องจาก CPU ต้องอ่านตำแหน่งหน่วยความจำในลูป อีกครั้ง: รหัสของฉันไม่มีการเพิ่มลูป

1
ฉันไม่สนใจเกี่ยวกับความเห็นของคุณเกี่ยวกับดีหรือไม่ดีมันได้รับการพิสูจน์แล้วว่าผิดหลายครั้งแล้ว อีกครั้ง: รหัสของฉันไม่เคยมีใครfor, whileหรือuntilลูป คุณเห็นสิ่งใดfor whileหรือuntilเขียนเป็นวงในโค้ดหรือไม่ ไม่งั้นแค่ยอมรับความจริงยอมรับมันและเรียนรู้

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