เป็นไปได้ไหมที่โปรแกรมจะรับจำนวนที่ว่างระหว่างอาร์กิวเมนต์บรรทัดคำสั่งใน POSIX?


23

พูดถ้าฉันเขียนโปรแกรมด้วยบรรทัดต่อไปนี้:

int main(int argc, char** argv)

argvตอนนี้ก็รู้ว่าสิ่งที่อาร์กิวเมนต์บรรทัดคำสั่งจะถูกส่งผ่านไปโดยการตรวจสอบเนื้อหาของ

โปรแกรมสามารถตรวจสอบว่ามีช่องว่างระหว่างอาร์กิวเมนต์กี่รายการ? เช่นเมื่อฉันพิมพ์เหล่านี้ในทุบตี:

ibug@linux:~ $ ./myprog aaa bbb
ibug@linux:~ $ ./myprog       aaa      bbb

Environment เป็น Linux ที่ทันสมัย ​​(เช่น Ubuntu 16.04) แต่ฉันคิดว่าคำตอบควรนำไปใช้กับระบบที่สอดคล้องกับ POSIX


22
เพื่อความอยากรู้อยากเห็นเหตุใดโปรแกรมของคุณจึงจำเป็นต้องรู้
nxnev

2
@nxnev ฉันเคยเขียนบางโปรแกรม Windows และฉันรู้ว่ามันเป็นไปได้ที่นั่นดังนั้นฉันสงสัยว่ามีบางสิ่งที่คล้ายกันใน Linux (หรือ Unix)
iBug

9
ฉันจำรางใน CP / M ที่โปรแกรมต้องแยกวิเคราะห์บรรทัดคำสั่งของตัวเองซึ่งหมายความว่า C runtime ทุกตัวต้องใช้ตัวแยกวิเคราะห์เชลล์ และพวกเขาทั้งหมดต่างกันเล็กน้อย
Toby Speight

3
@iBug มี แต่คุณต้องอ้างอาร์กิวเมนต์เมื่อเรียกใช้คำสั่ง นั่นเป็นวิธีที่ทำบน POSIX (และที่คล้ายกัน) เชลล์
Konrad Rudolph

3
@iBug, ... Windows มีการออกแบบเดียวกันกับที่ Toby กล่าวถึงจาก CP / M ด้านบน UNIX ไม่ได้ทำ - จากมุมมองของกระบวนการที่เรียกว่ามีเป็นบรรทัดคำสั่งที่ไม่มีส่วนร่วมในการทำงาน
Charles Duffy

คำตอบ:


39

ไม่มีความหมายที่จะพูดถึง "ช่องว่างระหว่างข้อโต้แย้ง"; นั่นเป็นแนวคิดของเชลล์

งานของเชลล์คือการรับทั้งบรรทัดของการป้อนข้อมูลและจัดรูปแบบลงในอาร์เรย์ของการขัดแย้งเพื่อเริ่มต้นคำสั่งด้วย สิ่งนี้อาจเกี่ยวข้องกับการแยกสตริงที่ยกมาตัวแปรการขยายไฟล์ไวด์การ์ดและการแสดงออกของตัวหนอนและอื่น ๆ คำสั่งเริ่มต้นด้วยการexecเรียกระบบมาตรฐานซึ่งยอมรับเวกเตอร์ของสตริง

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

เท่าที่คำสั่งถูกเรียกเกี่ยวข้องสิ่งที่เกิดขึ้นในเชลล์หรือกระบวนการ parent / precursor อื่น ๆ เป็นแบบส่วนตัวและซ่อนอยู่ - เราเห็นเฉพาะอาร์เรย์ของสตริงที่ C มาตรฐานระบุว่าmain()สามารถยอมรับได้


คำตอบที่ดี - มันเป็นสิ่งสำคัญที่จะต้องชี้ให้เห็นสิ่งนี้สำหรับมือใหม่ที่ใช้ระบบปฏิบัติการยูนิกซ์ซึ่งมักจะคิดว่าหากพวกเขารันtar cf texts.tar *.txtโปรแกรม tar จะได้รับสองข้อโต้แย้งและจะต้องขยายตัวที่สอง ( *.txt) หลายคนไม่ทราบว่ามันใช้งานได้จริงจนกระทั่งพวกเขาเริ่มเขียนสคริปต์ / โปรแกรมของตัวเองที่จัดการกับข้อโต้แย้ง
Laurence Renshaw

58

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


9
execve(2)คุณอาจต้องการที่จะพูดถึง
iBug

3
คุณพูดถูกที่เป็นข้ออ้างฉันสามารถพูดได้ว่าตอนนี้ฉันกำลังใช้โทรศัพท์อยู่และค้นหาหน้าคนเป็นเรื่องน่าเบื่อนิดหน่อย :-)
ฮันส์ - มาร์ติน Mosner

1
นี่คือส่วนที่เกี่ยวข้องของ POSIX
Stephen Kitt

1
@ Hans-MartinMosner: Termux ... ? ;-)
DevSolar

9
"โดยทั่วไป" หมายถึงการป้องกันการอ้างถึงกรณีที่ซับซ้อนพิเศษซึ่งเป็นไปได้ - ตัวอย่างเช่นกระบวนการรูท suid อาจสามารถตรวจสอบหน่วยความจำของเชลล์ที่เรียกใช้และค้นหาสตริงบรรทัดคำสั่งที่ไม่ได้แยกวิเคราะห์
Hans-Martin Mosner

16

ไม่เป็นไปไม่ได้ยกเว้นว่าช่องว่างเป็นส่วนหนึ่งของการโต้แย้ง

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

คำสั่งทั้งหมดบน Unix นั้นอยู่ในขั้นตอนสุดท้ายที่ดำเนินการโดยหนึ่งในexec()ตระกูลของฟังก์ชัน สิ่งเหล่านี้ใช้ชื่อคำสั่งและรายการหรืออาร์เรย์ของอาร์กิวเมนต์ ไม่มีใครรับบรรทัดคำสั่งดังที่พิมพ์ที่ shell prompt system()ฟังก์ชั่นไม่ แต่อาร์กิวเมนต์สตริงจะถูกดำเนินการในภายหลังโดยexecve()ที่อีกครั้งใช้เวลาอาร์เรย์ของการขัดแย้งมากกว่าสตริงบรรทัดคำสั่ง


2
@LightnessRacesinOrbit ฉันวางไว้ในนั้นในกรณีที่มีความสับสนเกี่ยวกับ "ช่องว่างระหว่างข้อโต้แย้ง" ใส่ช่องว่างในคำพูดระหว่างhelloและworldเป็นตัวอักษรช่องว่างระหว่างคนทั้งสองมีปากเสียง
Kusalananda

5
@Kusalananda - ดีไม่ ... วางช่องว่างในคำพูดระหว่างhelloและworldเป็นตัวอักษรจัดหาสองในสามของการขัดแย้ง
Jeremy

@ Jeremy อย่างที่ฉันพูดในกรณีที่มีความสับสนเกี่ยวกับสิ่งที่มีความหมายโดย "ระหว่างข้อโต้แย้ง" ใช่เป็นอาร์กิวเมนต์ที่สองระหว่างอีกสองถ้าคุณจะ
Kusalananda

ตัวอย่างของคุณดีและให้คำแนะนำ
Jeremy

1
พวกมันเป็นตัวอย่างที่ชัดเจนของความสับสนและความเข้าใจผิด ฉันได้ลบพวกเขาเนื่องจากไม่ได้เพิ่มมูลค่าของคำตอบ
Kusalananda

9

โดยทั่วไปแล้วเป็นไปไม่ได้เช่นเดียวกับคำตอบอื่น ๆ ที่อธิบายไว้

อย่างไรก็ตามUnix shellsเป็นโปรแกรมทั่วไป (และพวกมันกำลังตีความบรรทัดคำสั่งและทำให้กลมกลืน , นั่นคือการขยายคำสั่งก่อนที่จะทำfork& execveเพื่อมัน) เห็นนี้คำอธิบายเกี่ยวกับbashการดำเนินงานของเปลือก คุณสามารถเขียนเชลล์ของคุณเอง (หรือคุณสามารถแก้ไขเชลล์ซอฟต์แวร์ฟรีที่มีอยู่บางตัวเช่นGNU ทุบตี ) และใช้เป็นเชลล์ (หรือแม้แต่ล็อกอินเชลล์ของคุณดูpasswd (5) & shells (5) )

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

ฉันไม่เข้าใจว่าทำไมคุณถึงต้องการทำเช่นนั้น แต่คุณอาจใช้รหัสเชลล์ในลักษณะนี้ (แต่ฉันแนะนำไม่ให้ทำ)

BTW โปรแกรมอาจเริ่มโดยบางโปรแกรมที่ไม่ใช่เชลล์ (แต่ใช้fork (2)จากนั้นเรียกใช้งาน(2)หรือexecve (2)หรือเพียงexecveเพื่อเริ่มโปรแกรมในกระบวนการปัจจุบัน) ในกรณีที่ไม่มีบรรทัดคำสั่งเลยและโปรแกรมของคุณอาจเริ่มทำงานโดยไม่มีคำสั่ง ...

โปรดสังเกตว่าคุณอาจมีระบบลีนุกซ์ (พิเศษ) บางระบบโดยไม่ติดตั้งเชลล์ใด ๆ นี่มันแปลกและผิดปกติ แต่เป็นไปได้ จากนั้นคุณจะต้องเขียนโปรแกรมinitพิเศษเพื่อเริ่มโปรแกรมอื่น ๆ ตามที่ต้องการโดยไม่ต้องใช้เชลล์ใด ๆ แต่ต้องทำการfork& execveเรียกระบบ

อ่านเพิ่มเติมระบบปฏิบัติการ: สามชิ้นง่าย ๆและอย่าลืมว่าในexecveทางปฏิบัติแล้วการเรียกของระบบ (บนลีนุกซ์พวกมันจะอยู่ในsyscalls (2) , ดูที่บทนำ (2) ) ซึ่งเริ่มต้นพื้นที่แอดเดรสเสมือนใหม่อีกครั้ง สิ่งต่าง ๆ ) ของกระบวนการที่ทำ


นี่คือคำตอบที่ดีที่สุด ฉันถือว่า (โดยไม่ต้องดูว่าขึ้น) ว่าargv[0] สำหรับชื่อโปรแกรมและองค์ประกอบที่เหลือสำหรับอาร์กิวเมนต์เป็นข้อมูลจำเพาะ POSIX และไม่สามารถเปลี่ยนแปลงได้ สภาพแวดล้อมรันไทม์สามารถระบุargv[-1]สำหรับบรรทัดคำสั่งได้ฉันถือว่า ...
Peter - Reinstate Monica

ไม่มันทำไม่ได้ อ่านexecveเอกสารอย่างละเอียดมากขึ้น คุณไม่สามารถใช้argv[-1]มันเป็นพฤติกรรมที่ไม่ได้กำหนดที่จะใช้
Basile Starynkevitch

ใช่จุดที่ดี (เช่นคำใบ้ว่าเรามีตึกระฟ้า) - ความคิดนั้นค่อนข้างถูกต้อง ส่วนประกอบทั้งสามของรันไทม์ (เชลล์, stdlib และ OS) จะต้องทำงานร่วมกัน เชลล์จำเป็นต้องเรียกใช้execvepluscmdฟังก์ชั่นพิเศษที่ไม่ใช่ POSIX ด้วยพารามิเตอร์พิเศษ (หรือการประชุม argv) syscall สร้างเวกเตอร์อาร์กิวเมนต์สำหรับหลักที่มีตัวชี้ไปยังบรรทัดคำสั่งก่อนที่ตัวชี้ไปยังชื่อโปรแกรมแล้วผ่านที่อยู่ ของตัวชี้ไปยังชื่อโปรแกรมเช่นเดียวกับargvเมื่อเรียกโปรแกรมmain...
Peter - Reinstate Monica

ไม่จำเป็นต้องเขียนเชลล์อีกครั้งเพียงใช้เครื่องหมายคำพูด shคุณลักษณะนี้ได้จากเปลือก Bourn ดังนั้นไม่ใช่เรื่องใหม่
ctrl-alt-delor

การใช้อัญประกาศต้องเปลี่ยนบรรทัดคำสั่ง และ OP ไม่ต้องการสิ่งนั้น
Basile Starynkevitch

3

คุณสามารถบอกเชลล์ของคุณให้บอกแอปพลิเคชันว่าเชลล์โค้ดใดบ้างที่นำไปสู่การดำเนินการ ตัวอย่างเช่นด้วยการzshส่งข้อมูลใน$SHELL_CODEตัวแปรสภาพแวดล้อมโดยใช้preexec()hook ( printenvใช้เป็นตัวอย่างคุณจะใช้getenv("SHELL_CODE")ในโปรแกรมของคุณ):

$ preexec() export SHELL_CODE=$1
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv  SHELL_CODE
printenv  CODE
$ $(echo printenv SHELL_CODE)
$(echo printenv SHELL_CODE)
$ for i in SHELL_CODE; do printenv "$i"; done
for i in SHELL_CODE; do printenv "$i"; done
$ printenv SHELL_CODE; : other command
printenv SHELL_CODE; : other command
$ f() printenv SHELL_CODE
$ f
f

ทั้งหมดเหล่านั้นจะดำเนินการprintenvเป็น:

execve("/usr/bin/printenv", ["printenv", "SHELL_CODE"], 
       ["PATH=...", ..., "SHELL_CODE=..."]);

อนุญาตให้printenvเรียกคืนรหัส zsh ที่นำไปสู่การดำเนินการprintenvกับอาร์กิวเมนต์เหล่านั้น สิ่งที่คุณต้องการจะทำกับข้อมูลนั้นไม่ชัดเจนสำหรับฉัน

ด้วยbashคุณลักษณะที่ใกล้เคียงกับzsh's preexec()จะใช้มัน$BASH_COMMANDในDEBUGกับดัก แต่ทราบว่าbashจะระดับของการเขียนใหม่ในการที่ (และใน refactors เฉพาะบางส่วนของช่องว่างที่ใช้เป็นตัวคั่น) และที่นำไปใช้กับทุกคน (ดีพอใช้) บางคำสั่ง ทำงานไม่ใช่บรรทัดคำสั่งทั้งหมดตามที่ป้อนที่พรอมต์ (ดูเพิ่มเติมที่functraceตัวเลือก)

$ trap 'export SHELL_CODE="$BASH_COMMAND"' DEBUG
$ printenv SHELL_CODE
printenv SHELL_CODE
$ printenv $(echo 'SHELL_CODE')
printenv $(echo 'SHELL_CODE')
$ for i in SHELL_CODE; do printenv "$i"; done; : other command
printenv "$i"
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printf '%s\n' "$(printenv "SHELL_CODE")"
$ set -o functrace
$ printf '%s\n' "$(printenv "SHELL_CODE")"
printenv "SHELL_CODE"
$ print${-+env  }    $(echo     'SHELL_CODE')
print${-+env  } $(echo     'SHELL_CODE')

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

โปรดทราบว่าฉันไม่แนะนำให้ทำสิ่งนี้เนื่องจากคุณอาจรั่วไหลข้อมูลที่ละเอียดอ่อนไปยังทุกคำสั่งเช่นเดียวกับใน:

echo very_secret | wc -c | untrustedcmd

จะรั่วไหลความลับที่ทั้งสองและwcuntrustedcmd

แน่นอนคุณสามารถทำสิ่งนั้นกับภาษาอื่นที่ไม่ใช่เปลือก ตัวอย่างเช่นใน C คุณสามารถใช้มาโครบางตัวที่ส่งออกรหัส C ที่ดำเนินการคำสั่งไปยังสภาพแวดล้อม:

#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#define WRAP(x) (setenv("C_CODE", #x, 1), x)

int main(int argc, char *argv[])
{
  if (!fork()) WRAP(execlp("printenv", "printenv", "C_CODE", NULL));
  wait(NULL);
  if (!fork()) WRAP(0 + execlp("printenv",   "printenv", "C_CODE", NULL));
  wait(NULL);
  if (argc > 1 && !fork()) WRAP(execvp(argv[1], &argv[1]));
  wait(NULL);
  return 0;
}

ตัวอย่าง:

$ ./a.out printenv C_CODE
execlp("printenv", "printenv", "C_CODE", NULL)
0 + execlp("printenv", "printenv", "C_CODE", NULL)
execvp(argv[1], &argv[1])

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


เมื่อฉันกำลังทดสอบสิ่งนี้BASH_COMMANDไม่มีข้อโต้แย้งแยกช่องว่างดั้งเดิมดังนั้นนี่จึงไม่สามารถใช้งานได้สำหรับคำขอตามตัวอักษรของ OP คำตอบนี้รวมถึงการสาธิตวิธีใด ๆ สำหรับกรณีการใช้งานเฉพาะนั้นหรือไม่?
Charles Duffy

@CharlesDuffy ฉันแค่ต้องการระบุ preexec ของ zsh ที่ใกล้เคียงที่สุด () ใน bash (เนื่องจากเชลล์ที่ OP อ้างถึง) และชี้ให้เห็นว่าไม่สามารถใช้สำหรับกรณีการใช้งานเฉพาะนั้นได้ แต่ฉันเห็นด้วยไม่ใช่ ชัดเจนมาก ดูการแก้ไข คำตอบนี้มีจุดประสงค์ทั่วไปมากขึ้นเกี่ยวกับวิธีการส่งซอร์สโค้ด (ที่นี่ใน zsh / bash / C) ที่ทำให้เกิดการดำเนินการกับคำสั่งที่ถูกดำเนินการ (ไม่ใช่สิ่งที่มีประโยชน์ แต่ฉันหวังว่าในขณะที่ทำเช่นนั้นและโดยเฉพาะ ด้วยตัวอย่างฉันแสดงให้เห็นว่ามันไม่มีประโยชน์มาก)
Stéphane Chazelas

0

ฉันจะเพิ่มสิ่งที่ขาดหายไปในคำตอบอื่น ๆ

ไม่

ดูคำตอบอื่น ๆ

อาจจะเรียงลำดับของ

ไม่มีสิ่งใดที่สามารถทำได้ในโปรแกรม แต่มีบางอย่างที่สามารถทำได้ในเชลล์เมื่อคุณรันโปรแกรม

คุณต้องใช้เครื่องหมายคำพูด ดังนั้นแทนที่จะ

./myprog      aaa      bbb

คุณต้องทำอย่างใดอย่างหนึ่งเหล่านี้

./myprog "     aaa      bbb"
./myprog '     aaa      bbb'

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

ตัวเลือก 2

ส่งผ่านข้อมูลในทาง stdin นี่เป็นวิธีปกติในการรับข้อมูลจำนวนมากเข้าสู่คำสั่ง เช่น

./myprog << EOF
    aaa      bbb
EOF

หรือ

./myprog
Tell me what you want to tell me:
aaaa bbb
ctrl-d

(ตัวเอียงเป็นผลลัพธ์ของโปรแกรม)


ในทางเทคนิคแล้วรหัสเชลล์: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"เรียกใช้งาน (โดยทั่วไปในกระบวนการลูก) ไฟล์ที่เก็บไว้ใน./myprogและผ่านมันสองข้อโต้แย้ง: ./myprogและ␣␣␣␣␣aaa␣␣␣␣␣␣bbb(argv[0]และargc[1] , argcเป็น 2) และในขณะที่ OP ของพื้นที่ที่แยกทั้งสองมีปากเสียงไม่ผ่านในทางใดทางหนึ่ง myprogไปยัง
Stéphane Chazelas

แต่คุณกำลังเปลี่ยนคำสั่งและ OP ไม่ต้องการเปลี่ยนมัน
Basile Starynkevitch

@BasileStarynkevitch ตามความคิดเห็นของคุณฉันอ่านคำถามอีกครั้ง คุณกำลังตั้งสมมติฐาน OP ไม่มีที่ไหนเลยที่บอกว่าพวกเขาไม่ต้องการเปลี่ยนวิธีการทำงานของโปรแกรม บางทีนี่อาจเป็นเรื่องจริง แต่พวกเขาก็ไม่มีอะไรจะพูด ดังนั้นคำตอบนี้อาจเป็นสิ่งที่พวกเขาต้องการ
ctrl-alt-delor

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