เหตุใด argv จึงรวมชื่อโปรแกรม


106

โปรแกรม Unix / Linux ทั่วไปยอมรับอินพุตบรรทัดคำสั่งเป็นจำนวนอาร์กิวเมนต์ ( int argc) และเวกเตอร์อาร์กิวเมนต์ ( char *argv[]) องค์ประกอบแรกของargvคือชื่อโปรแกรม - ตามด้วยข้อโต้แย้งที่เกิดขึ้นจริง

ทำไมชื่อโปรแกรมที่ส่งผ่านไปยังไฟล์ที่เรียกทำงานได้เป็นอาร์กิวเมนต์? มีตัวอย่างของโปรแกรมที่ใช้ชื่อของตัวเอง (อาจเป็นexecสถานการณ์บางอย่าง) หรือไม่?


6
ชอบ mv และ cp ไหม
Archemar

9
ใน Debian shเป็น symlink dashไป พวกเขาทำงานแตกต่างกันเมื่อถูกเรียกว่าเป็นshหรือdash
Motte001

21
@AlexejMagura หากคุณใช้สิ่งที่ต้องการbusybox(พบได้ทั่วไปในแผ่นดิสก์กู้ภัยและอื่น ๆ ) แล้วทุกอย่างก็สวยมาก(cp, mv, rm, ls, ... ) เป็นลิงก์สัญลักษณ์ไปยัง busybox
Baard Kopperud

11
ฉันพบนี้จริงๆยากที่จะละเว้นดังนั้นฉันจะบอกว่ามัน: คุณอาจหมายถึง "GNU" โปรแกรม ( gcc, bash, gunzip, ส่วนที่เหลือของระบบปฏิบัติการที่ ... ), ลินุกซ์เป็นเพียงเคอร์เนล
wizzwizz4

10
@ wizzwizz4 มีอะไรผิดปกติกับ "โปรแกรมทั่วไป Unix / Linux"? ฉันอ่านมันเหมือน "โปรแกรมทั่วไปที่ทำงานบน Unix / Linux" ดีกว่าข้อ จำกัด ของคุณสำหรับโปรแกรม GNU บางโปรแกรม Dennis Ritchie ไม่ได้ใช้โปรแกรม GNU ใด ๆ BTW the Hurd kernel เป็นตัวอย่างของโปรแกรม GNU ซึ่งไม่ได้มีฟังก์ชั่นหลัก ...
rudimeier

คำตอบ:


122

ในการเริ่มต้นโปรดทราบว่าargv[0]ไม่จำเป็นต้องใช้ชื่อโปรแกรม มันเป็นสิ่งที่ผู้โทรเข้ามาในargv[0]การexecveเรียกของระบบ (เช่นดูคำถามนี้ในกองล้น ) (ตัวแปรอื่น ๆ ทั้งหมดของexecไม่ใช่การเรียกของระบบ แต่เชื่อมต่อกับexecve)

ตัวอย่างเช่นสมมติว่าต่อไปนี้ (โดยใช้execl):

execl("/var/tmp/mybackdoor", "top", NULL);

/var/tmp/mybackdoorคือสิ่งที่ถูกดำเนินการ แต่argv[0]ถูกตั้งค่าเป็นtopและนี่คือสิ่งที่ps(จริง) topจะแสดง ดูคำตอบนี้ใน U&L SE สำหรับข้อมูลเพิ่มเติม

การตั้งค่าทั้งหมดนี้กัน: ก่อนการถือกำเนิดของระบบไฟล์แฟนซีเช่น/proc, argv[0]เป็นวิธีเดียวสำหรับกระบวนการเรียนรู้เกี่ยวกับชื่อของตัวเอง สิ่งที่จะดีสำหรับ

  • หลายโปรแกรมปรับแต่งพฤติกรรมของพวกเขาขึ้นอยู่กับชื่อที่พวกเขาถูกเรียก (โดยปกติจะเป็นสัญลักษณ์หรือลิงก์ยาก ๆ เช่นโปรแกรมอรรถประโยชน์ของ BusyBoxและมีตัวอย่างอีกหลายตัวอย่างไว้ในคำตอบของคำถามนี้)
  • นอกจากนี้บริการ daemons และโปรแกรมอื่น ๆ ที่เข้าสู่ระบบผ่าน syslog มักจะเติมชื่อของพวกเขาไปยังรายการบันทึก; หากไม่มีสิ่งนี้การติดตามกิจกรรมจะกลายเป็นสิ่งที่ไม่สามารถเข้าถึงได้

18
ตัวอย่างของโปรแกรมดังกล่าวbunzip2, bzcatและbzip2เพื่อที่แรกที่สองเป็น symlinks ไปหนึ่งในสาม
Ruslan

5
@Ruslan ที่น่าสนใจzcatไม่ใช่ symlink ดูเหมือนว่าพวกเขาจะหลีกเลี่ยงข้อเสียของเทคนิคนี้โดยใช้เชลล์สคริปต์แทน แต่พวกเขาล้มเหลวในการพิมพ์--helpผลลัพธ์ที่สมบูรณ์เพราะคนที่เพิ่มตัวเลือกใน gzip ลืมที่จะรักษา zcat ด้วย
rudimeier

1
ตราบใดที่ฉันยังจำได้มาตรฐานการเข้ารหัสของ GNU ไม่สนับสนุนการใช้ argv [0] เพื่อเปลี่ยนพฤติกรรมของโปรแกรม ( ส่วน "มาตรฐานสำหรับการเชื่อมต่อโดยทั่วไป" ในรุ่นปัจจุบัน ) gunzipเป็นข้อยกเว้นทางประวัติศาสตร์

19
busybox เป็นอีกตัวอย่างที่ยอดเยี่ยม สามารถเรียกชื่อได้ 308 ชื่อเพื่อเรียกใช้คำสั่งต่าง ๆ : busybox.net/downloads/BusyBox.html#commands
Schmitz

2
โปรแกรมจำนวนมากอีกหลายโปรแกรมยังแทรกargv[0]การใช้งาน / ความช่วยเหลือเอาท์พุทแทนที่จะเข้ารหัสอย่างหนักชื่อของพวกเขา บางส่วนเต็มบางส่วนเป็นเพียงชื่อฐาน
spectras

62

มากมาย:

  • ทุบตีทำงานในโหมด POSIXเมื่อเป็นargv[0] shมันทำงานเป็นเปลือกเข้าสู่ระบบเมื่อเริ่มต้นด้วยargv[0]-
  • เป็นกลุ่มทำงานแตกต่างกันเมื่อใช้เป็นvi, view, evim, eview, ex, vimdiffฯลฯ
  • Busybox ดังที่กล่าวไปแล้ว
  • ในระบบที่มี systemd เป็น init, shutdown, rebootฯลฯ เป็นsymlinks systemctlไป
  • และอื่น ๆ

7
อีกอันหนึ่งคือsendmailและmail. ทุกยูนิกซ์ MTA มาพร้อมกับ symlink สำหรับทั้งสองคำสั่งและได้รับการออกแบบมาเพื่อเลียนแบบพฤติกรรมดั้งเดิมเมื่อถูกเรียกเช่นนี้ซึ่งหมายความว่าโปรแกรม unix ใด ๆ ที่ต้องการส่งเมลจะรู้ได้อย่างไรว่าพวกเขาสามารถทำได้
Shadur

4
กรณีทั่วไปอื่น ๆ : testและ[: ]เมื่อคุณเรียกอดีตจะจัดการกับข้อผิดพลาดถ้าอาร์กิวเมนต์สุดท้ายคือ (บนเดเบียนเสถียรจริงคำสั่งเหล่านี้เป็นสองโปรแกรมที่แตกต่างกัน แต่รุ่นก่อนหน้าและ MacO ยังคงใช้โปรแกรมเดียวกัน) และtex, latexและอื่น ๆ : ไบนารีคือเดียวกัน แต่มองว่ามันถูกเรียกว่ามันเลือกที่เหมาะสมการตั้งค่าไฟล์ initคล้ายกัน.
Giacomo Catenazzi

4
ที่เกี่ยวข้องกับการ[คิดว่ามันเป็นข้อผิดพลาดถ้าอาร์กิวเมนต์สุดท้ายคือไม่ได้ ]
chepner

ฉันเดาว่านี่จะตอบคำถามที่สอง แต่ไม่ใช่คำถามแรก ฉันสงสัยอย่างมากที่ผู้ออกแบบระบบปฏิบัติการบางคนนั่งลงและพูดว่า "เฮ้มันคงจะเจ๋งถ้าฉันมีโปรแกรมเดียวกันที่ทำสิ่งต่าง ๆ โดยใช้ชื่อปฏิบัติการ ฉันเดาว่าฉันจะรวมชื่อไว้ในอาเรย์อาร์กิวเมนต์ของมันแล้ว«
โจอี้

@ โจอี้ถ้อยคำนี้มีวัตถุประสงค์เพื่อสื่อความหมายนั้น (Q: "มีอะไรบ้าง ... " A: "Plenty: ... ")
muru

34

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

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

แก้ไข : การอ้างอิงสำหรับ Unix รุ่นที่ 1 ตามที่ร้องขอ

เราสามารถเห็นได้จากฟังก์ชั่นหลักของccมันargcและargvถูกใช้ไปแล้ว เปลือกสำเนาข้อโต้แย้งไปparbufภายในnewargเป็นส่วนหนึ่งของวงในขณะที่การรักษาคำสั่งตัวเองในลักษณะเดียวกับข้อโต้แย้ง (แน่นอนว่าหลังจากนั้นมันรันเฉพาะอาร์กิวเมนต์แรกซึ่งเป็นชื่อของคำสั่ง) ดูเหมือนว่าexecvและญาติไม่ได้อยู่แล้ว


1
โปรดเพิ่มการอ้างอิงที่สำรองข้อมูลนี้
Lesmana

จาก skimming รวดเร็วexecใช้ชื่อของคำสั่งการดำเนินการและอาร์เรย์ศูนย์สิ้นสุดของตัวชี้ถ่าน (เห็นที่ดีที่สุดในminnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.sที่execต้องใช้เวลา อ้างอิงถึงป้ายที่ 2 และป้ายที่ 1 และที่ฉลาก2:จะปรากฏขึ้นetc/init\0และในฉลาก1:ปรากฏการอ้างอิงไปยังป้าย 2 และยุติศูนย์) ซึ่งเป็นเป็นสิ่งที่ไม่ลบในวันนี้execve envp
ninjalj

1
execvและexeclมีอยู่ "ตลอดไป" (เช่นตั้งแต่ต้นถึงกลางปี ​​1970) - execvเป็นการเรียกของระบบและexeclเป็นฟังก์ชั่นห้องสมุดที่เรียกมันว่า   execveไม่มีอยู่เนื่องจากสภาพแวดล้อมนั้นไม่มีอยู่จริง สมาชิกคนอื่น ๆ ในครอบครัวถูกเพิ่มเข้ามาในภายหลัง
G-Man

@ G-Man คุณสามารถชี้ให้ฉันเห็นexecvในแหล่งที่มาของ v1 ที่ฉันเชื่อมโยงได้หรือไม่ แค่สงสัย.
dirkt

22

ใช้กรณี:

คุณสามารถใช้ชื่อโปรแกรมที่จะเปลี่ยนพฤติกรรมโปรแกรม

ตัวอย่างเช่นคุณสามารถสร้าง symlink ไปยังไบนารีจริงได้

ตัวอย่างที่มีชื่อเสียงหนึ่งที่ใช้เทคนิคนี้คือโครงการ busybox ซึ่งติดตั้งไบนารีเดียวและ symlink มากมายให้กับมัน (ls, cp, mv, ฯลฯ ) พวกเขากำลังทำเพื่อประหยัดพื้นที่เก็บข้อมูลเนื่องจากเป้าหมายเป็นอุปกรณ์ฝังตัวขนาดเล็ก

สิ่งนี้ยังใช้ในsetarchจาก util-linux:

$ ls -l /usr/bin/ | grep setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 i386 -> setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 linux32 -> setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 linux64 -> setarch
-rwxr-xr-x 1 root root       14680 2015-10-22 16:54 setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 x86_64 -> setarch

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

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

นอกจากนี้ยังมีหลายโปรแกรมพิมพ์ข้อความผิดพลาดรวมทั้งชื่อโปรแกรม

ทำไม :

  1. เนื่องจากเป็นการประชุม POSIX ( man 3p execve):

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

  1. เป็นมาตรฐาน C (อย่างน้อย C99 และ C11):

หากค่าของ argc มากกว่าศูนย์สตริงที่ชี้ไปโดย argv [0] จะแทนชื่อโปรแกรม argv [0] [0] จะต้องเป็นตัวละคร null ถ้าชื่อโปรแกรมไม่สามารถใช้ได้จากสภาพแวดล้อมโฮสต์

หมายเหตุ C Standard บอกว่า "ชื่อโปรแกรม" ไม่ใช่ "ชื่อไฟล์"


3
นี่ไม่ใช่การหยุดพักถ้าคุณเข้าถึง symlink จาก symlink อื่นหรือไม่
Mehrdad

3
@ Mehrdad ใช่นั่นเป็นข้อเสียและอาจสร้างความสับสนให้กับผู้ใช้
rudimeier

@rudimeier: รายการ 'ทำไม' ของคุณไม่ใช่เหตุผลจริงๆพวกเขาเป็นเพียง "homunculus" นั่นคือเพียงแค่ถามคำถามว่าทำไมมาตรฐานถึงต้องการสิ่งนี้
einpoklum

@einpoklum คำถามของ OP คือ: ทำไมชื่อโปรแกรมจึงถูกส่งไปยังโปรแกรมที่เรียกทำงานได้ ฉันตอบ: เนื่องจาก POSIX และ C มาตรฐานบอกให้เราทำ คุณคิดว่านั่นไม่ใช่เหตุผลจริงหรือ หากเอกสารที่ฉันยกมาไม่มีอยู่คงเป็นไปได้ว่าหลายโปรแกรมจะไม่ผ่านชื่อโปรแกรม
rudimeier

OP กำลังถามอย่างมีประสิทธิภาพ "ทำไม POSIX และมาตรฐาน C ถึงบอกว่าทำเช่นนี้?" ได้รับข้อความอยู่ในระดับที่เป็นนามธรรม แต่ดูเหมือนชัดเจน วิธีเดียวที่จะรู้ได้คือถามผู้สร้าง
user2338816

21

นอกเหนือจากโปรแกรมที่เปลี่ยนแปลงพฤติกรรมของพวกเขาขึ้นอยู่กับวิธีที่พวกเขาถูกเรียกฉันพบว่าargv[0]มีประโยชน์ในการพิมพ์การใช้งานของโปรแกรมเช่น:

printf("Usage: %s [arguments]\n", argv[0]);

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

# cat foo.c 
#include <stdio.h>
int main(int argc, char **argv) { printf("Usage: %s [arguments]\n", argv[0]); }
# gcc -Wall -o foo foo.c
# mv foo /usr/bin 
# cd /usr/bin 
# ln -s foo bar
# foo
Usage: foo [arguments]
# bar
Usage: bar [arguments]
# ./foo
Usage: ./foo [arguments]
# /usr/bin/foo
Usage: /usr/bin/foo [arguments]

มันเป็นสัมผัสที่ดีโดยเฉพาะอย่างยิ่งสำหรับเครื่องมือ / สคริปต์วัตถุประสงค์พิเศษขนาดเล็กที่อาจมีอยู่ทั่วทุกที่

ดูเหมือนว่าการปฏิบัติทั่วไปในเครื่องมือ GNU เช่นกันดูlsตัวอย่าง:

% ls --qq
ls: unrecognized option '--qq'
Try 'ls --help' for more information.
% /bin/ls --qq
/bin/ls: unrecognized option '--qq'
Try '/bin/ls --help' for more information.

3
+1 ฉันจะแนะนำเหมือนกัน แปลกที่คนจำนวนมากให้ความสนใจกับการเปลี่ยนแปลงพฤติกรรมและไม่พูดถึงอาจเป็นการใช้ที่ชัดเจนที่สุดและแพร่หลายมากขึ้น
Vee

5

program_name0 arg1 arg2 arg3 ...หนึ่งดำเนินการพิมพ์โปรแกรม:

ดังนั้นเชลล์ควรแบ่งโทเค็นแล้วและโทเค็นแรกนั้นมีชื่อโปรแกรมอยู่แล้ว และ BTW มีดัชนีเดียวกันทั้งด้านโปรแกรมและบนเชลล์

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


4

โดยทั่วไปแล้ว argv จะรวมชื่อโปรแกรมเพื่อให้คุณสามารถเขียนข้อความแสดงข้อผิดพลาดเช่นprgm: file: No such file or directoryซึ่งจะนำไปใช้กับสิ่งนี้:

    fprintf( stderr, "%s: %s: No such file or directory\n", argv[0], argv[1] );

2

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

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char** argv) {

  (void) argc;

  printf("arg: %s\n", argv[1]);
  int count = atoi(argv[1]);

  if ( getchar() == 'y' ) {

    ++count;

    char buf[20];
    sprintf(buf, "%d", count);

    char* newargv[3];
    newargv[0] = argv[0];
    newargv[1] = buf;
    newargv[2] = NULL;

    execve(argv[0], newargv, NULL);
  }

  return count;
}

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

ตัวอย่าง:

$ ./res 1
arg: 1
y
arg: 2
y
arg: 3
y
arg: 4
y
arg: 5
y
arg: 6
y
arg: 7
n

7 | $

แหล่งที่มาและบางข้อมูลเพิ่มเติม


ขอแสดงความยินดีที่มีจำนวนถึง 1,000
G-Man

0

พา ธ ไปยังโปรแกรมคือargv[0]เพื่อให้โปรแกรมสามารถดึงไฟล์การกำหนดค่า ฯลฯ จากไดเรกทอรีการติดตั้ง นี้จะเป็นไปไม่ได้โดยไม่ต้อง
argv[0]


2
นั่นไม่ใช่คำอธิบายที่ดีโดยเฉพาะอย่างยิ่ง - มีเหตุผลที่เราอาจจะไม่ได้มาตรฐานในบางสิ่งบางอย่างที่ไม่เหมือนใคร(char *path_to_program, char **argv, int argc)เช่น
moopet

afaik โปรแกรมส่วนใหญ่ดึงกำหนดค่าจากสถานที่ตั้งมาตรฐาน ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) และทั้งใช้พารามิเตอร์ที่จะเปลี่ยนแปลงหรือมีตัวเลือกที่รวบรวมเวลาที่อบในอย่างต่อเนื่องเพื่อไบนารี
Xiong Chiamiov

0

ccacheทำงานในลักษณะนี้เพื่อเลียนแบบการโทรต่าง ๆ ไปยังไบนารีคอมไพเลอร์ ccache เป็นคอมไพล์แคช - จุดทั้งหมดจะไม่คอมไพล์ซอร์สโค้ดเดียวกันสองครั้ง แต่จะส่งคืนโค้ดอ็อบเจ็กต์จากแคชหากเป็นไปได้

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

วิธี symlink เกี่ยวข้องกับการเรียกใช้คำสั่งเหล่านี้:

cp ccache /usr/local/bin/
ln -s ccache /usr/local/bin/gcc
ln -s ccache /usr/local/bin/g++
ln -s ccache /usr/local/bin/cc
ln -s ccache /usr/local/bin/c++
... etc ...

... ผลกระทบที่จะอนุญาตให้ ccache ขัดขวางคำสั่งใด ๆ ที่จะไปถึงคอมไพเลอร์ดังนั้นการอนุญาตให้ ccache ส่งคืนไฟล์แคชหรือส่งคำสั่งไปยังคอมไพเลอร์จริง

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