Tại sao argv bao gồm tên chương trình?


106

Các chương trình Unix / Linux điển hình chấp nhận các đầu vào dòng lệnh dưới dạng đếm đối số ( int argc) và vectơ đối số ( char *argv[]). Phần tử đầu tiên của argvlà tên chương trình - theo sau là các đối số thực tế.

Tại sao tên chương trình được truyền cho tệp thực thi như một đối số? Có bất kỳ ví dụ về các chương trình sử dụng tên riêng của họ (có thể một số loại exectình huống)?


6
như mv và cp?
Archemar

9
Trên Debian shlà symlink tới dash. Họ cư xử khác nhau, khi được gọi là shhoặc nhưdash
Motte001

21
@AlexejMagura Nếu bạn sử dụng một cái gì đó như busybox(phổ biến trên đĩa cứu hộ và những thứ tương tự), thì mọi thứ khá nhiều (cp, mv, rm, ls, ...) là một liên kết tượng trưng đến busybox.
Baard Kopperud

11
Tôi đang tìm kiếm này thực sự khó có thể bỏ qua, vì vậy tôi sẽ nói nó: bạn có thể có nghĩa là chương trình "GNU" ( gcc, bash, gunzip, hầu hết các phần còn lại của hệ điều hành ...), như Linux chỉ là hạt nhân.
wizzwizz4

10
@ wizzwizz4 Có gì sai với "Chương trình Unix / Linux điển hình"? Tôi đọc nó như "Các chương trình điển hình chạy trên Unix / Linux". Điều đó tốt hơn nhiều so với hạn chế của bạn đối với các chương trình GNU nhất định. Dennis Ritchie chắc chắn không sử dụng bất kỳ chương trình GNU nào. BTW kernel Hurd là một ví dụ về chương trình GNU không có chức năng chính ...
rudimeier

Câu trả lời:


122

Để bắt đầu, lưu ý rằng argv[0]không nhất thiết phải là tên chương trình. Đó là những gì người gọi đặt thành argv[0]của execvecuộc gọi hệ thống (ví dụ như nhìn thấy câu hỏi này trên Stack Overflow ). (Tất cả các biến thể khác execkhông phải là các cuộc gọi hệ thống mà là các giao diện execve.)

Giả sử, ví dụ, sau đây (sử dụng execl):

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

/var/tmp/mybackdoorlà những gì được thực thi nhưng argv[0]được đặt thành topvà đây là những gì pshoặc (thực) topsẽ hiển thị. Xem câu trả lời này trên U & L SE để biết thêm về điều này.

Đặt tất cả những điều này sang một bên: Trước khi các hệ thống tập tin ưa thích ra đời /proc, argv[0]là cách duy nhất để một quá trình tìm hiểu về tên của chính nó. Điều đó sẽ tốt cho cái gì?

  • Một số chương trình tùy chỉnh hành vi của họ tùy thuộc vào tên mà họ được gọi (thường là bằng các liên kết tượng trưng hoặc liên kết cứng, ví dụ như các tiện ích của BusyBox ; một số ví dụ khác được cung cấp trong các câu trả lời khác cho câu hỏi này).
  • Hơn nữa, các dịch vụ, trình nền và các chương trình khác đăng nhập qua syslog thường thêm tên của chúng vào các mục nhật ký; không có điều này, theo dõi sự kiện sẽ trở thành không khả thi.

18
Ví dụ về các chương trình như vậy bunzip2, bzcatbzip2, trong đó hai cái đầu tiên là liên kết tượng trưng cho cái thứ ba.
Ruslan

5
@Ruslan Thú vị zcatkhông phải là một liên kết tượng trưng. Thay vào đó, họ dường như tránh được nhược điểm của kỹ thuật này khi sử dụng tập lệnh shell. Nhưng họ không thể in một --helpđầu ra hoàn chỉnh vì ai đó đã thêm tùy chọn vào gzip cũng quên duy trì zcat.
rudimeier

1
Miễn là tôi có thể nhớ, các tiêu chuẩn mã hóa GNU đã không khuyến khích sử dụng argv [0] để thay đổi hành vi chương trình ( phần "Tiêu chuẩn cho giao diện nói chung" trong phiên bản hiện tại ). gunziplà một ngoại lệ lịch sử.

19
busybox là một ví dụ tuyệt vời khác. Nó có thể được gọi bằng 308 tên khác nhau để gọi các lệnh khác nhau: busybox.net/doads/BusyBox.html#commands
Pepijn Schmitz

2
Nhiều, nhiều chương trình khác cũng đưa argv[0]vào đầu ra sử dụng / trợ giúp của họ thay vì mã hóa cứng tên của họ. Một số đầy đủ, một số chỉ là tên cơ sở.
quang phổ

62

Rất nhiều

  • Bash chạy trong chế độ POSIX khi argv[0]sh. Nó chạy như một vỏ đăng nhập khi argv[0]bắt đầu với -.
  • Vim cư xử khác nhau khi chạy như vi, view, evim, eview, ex, vimdiff,, vv
  • Busybox, như đã đề cập.
  • Trong các hệ thống với systemd như init, shutdown, reboot, vv là symlink đếnsystemctl .
  • vân vân

7
Một số khác là sendmailmail. Mỗi MTA unix đi kèm với một liên kết tượng trưng cho hai lệnh đó và được thiết kế để mô phỏng hành vi của bản gốc khi được gọi như vậy, có nghĩa là bất kỳ chương trình unix nào cần gửi thư đều biết chính xác cách chúng có thể làm như vậy.
Shadur

4
một trường hợp phổ biến khác: test[: khi bạn gọi trước đây, nó sẽ xử lý lỗi nếu đối số cuối cùng là ]. (trên ổn định Debian thực tế, các lệnh này là hai chương trình khác nhau, nhưng các phiên bản trước đó và MacO vẫn sử dụng cùng một chương trình). Và tex, latexv.v.: nhị phân là như nhau, nhưng nhìn cách nó được gọi, nó chọn tệp cấu hình phù hợp . initcũng tương tự
Giacomo Catenazzi

4
Liên quan, [coi đó là một lỗi nếu đối số cuối cùng là không ] .
chepner

Tôi đoán điều này trả lời câu hỏi thứ hai, nhưng không phải câu hỏi đầu tiên. Tôi rất nghi ngờ một số nhà thiết kế hệ điều hành đã ngồi xuống và nói »Này, thật tuyệt nếu tôi có cùng một chương trình làm những việc khác nhau chỉ dựa trên tên thực thi của nó. Tôi đoán tôi sẽ bao gồm tên trong mảng đối số của nó, sau đó. «
Joey

@Joey Vâng, từ ngữ được dự định truyền đạt điều đó (Q: "Có bất kỳ ...?" A: "Rất nhiều: ...")
muru

34

Trong lịch sử, argvchỉ là một loạt các con trỏ đến "từ" của dòng lệnh, vì vậy nó có ý nghĩa để bắt đầu với "từ" đầu tiên, đó là tên của chương trình.

Và có khá nhiều chương trình hoạt động khác nhau tùy theo tên được sử dụng để gọi chúng, vì vậy bạn chỉ cần tạo các liên kết khác nhau đến chúng và nhận các "lệnh" khác nhau. Ví dụ cực đoan nhất mà tôi có thể nghĩ đến là busybox , hoạt động giống như vài chục "lệnh" khác nhau tùy thuộc vào cách gọi của nó .

Chỉnh sửa : Tài liệu tham khảo cho phiên bản Unix 1, theo yêu cầu

Người ta có thể thấy ví dụ từ chức năng chính của ccđiều đó argcargvđã được sử dụng. Các vỏ bản đối số cho các parbufbên trong newargmột phần của vòng lặp, trong khi điều trị các lệnh riêng của mình theo cách tương tự như các đối số. (Tất nhiên, sau này nó chỉ thực thi đối số đầu tiên, đó là tên của lệnh). Có vẻ như execvvà họ hàng đã không tồn tại sau đó.


1
vui lòng thêm tài liệu tham khảo sao lưu này.
lesmana

Từ việc lướt nhanh, execlấy tên của lệnh để thực thi và một mảng con trỏ char kết thúc bằng 0 (được thấy rõ nhất tại minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , trong đó execmất các tham chiếu đến nhãn 2 và nhãn 1, và tại nhãn 2:xuất hiện etc/init\0, và tại nhãn 1:xuất hiện một tham chiếu đến nhãn 2 và số 0 kết thúc), về cơ bản là những gì execvengày nay trừ đi envp.
ninjalj

1
execvexeclđã tồn tại "mãi mãi" (tức là từ đầu đến giữa những năm 1970) - execvlà một cuộc gọi hệ thống và execllà một chức năng thư viện gọi nó.   execvesau đó không tồn tại vì môi trường không tồn tại. Các thành viên khác trong gia đình đã được thêm vào sau đó.
G-Man

@ G-Man Bạn có thể chỉ cho tôi execvtrong nguồn v1 tôi đã liên kết không? Chỉ tò mò thôi.
dirkt

22

Trường hợp sử dụng:

Bạn có thể sử dụng tên chương trình để thay đổi hành vi của chương trình .

Ví dụ, bạn có thể tạo một số liên kết tượng trưng cho nhị phân thực tế.

Một ví dụ nổi tiếng trong đó kỹ thuật này được sử dụng là dự án busybox chỉ cài đặt một nhị phân duy nhất và nhiều liên kết tượng trưng cho nó. (ls, cp, mv, v.v.). Họ đang làm điều đó để tiết kiệm không gian lưu trữ vì mục tiêu của họ là các thiết bị nhúng nhỏ.

Điều này cũng được sử dụng trong setarchtừ linux-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

Ở đây họ đang sử dụng kỹ thuật này về cơ bản để tránh nhiều tệp nguồn trùng lặp hoặc chỉ để giữ cho các nguồn dễ đọc hơn.

Một trường hợp sử dụng khác sẽ là một chương trình cần tải một số mô-đun hoặc dữ liệu khi chạy. Có đường dẫn chương trình giúp bạn có thể tải các mô-đun từ một đường dẫn liên quan đến vị trí chương trình .

Hơn nữa, nhiều chương trình in thông báo lỗi bao gồm tên chương trình .

Tại sao :

  1. Bởi vì đó là quy ước POSIX ( man 3p execve):

argv là một chuỗi các chuỗi đối số được truyền cho chương trình mới. Theo quy ước, chuỗi đầu tiên trong số này phải chứa tên tệp được liên kết với tệp đang được thực thi.

  1. Đó là tiêu chuẩn C (ít nhất là C99 và C11):

Nếu giá trị của argc lớn hơn 0, chuỗi được trỏ bởi argv [0] đại diện cho tên chương trình; argv [0] [0] sẽ là ký tự null nếu tên chương trình không có sẵn từ môi trường máy chủ.

Lưu ý C Standard cho biết "tên chương trình" không phải "tên tệp".


3
Điều này không bị phá vỡ nếu bạn đạt được liên kết tượng trưng từ một liên kết tượng trưng khác?
Mehrdad

3
@Mehrdad, Có, đó là nhược điểm và có thể gây nhầm lẫn cho người dùng.
rudimeier

@rudimeier: Các mục 'Tại sao' của bạn không thực sự là lý do, chúng chỉ là một "homunculus", tức là nó chỉ đặt ra câu hỏi tại sao tiêu chuẩn lại yêu cầu điều này xảy ra.
einpoklum

@einpoklum Câu hỏi của OP là: Tại sao tên chương trình được chuyển cho tệp thực thi? Tôi trả lời: Bởi vì tiêu chuẩn POSIX và C bảo chúng tôi làm như vậy. Làm thế nào bạn nghĩ rằng đó không thực sự là một lý do ? Nếu các tài liệu tôi đã trích dẫn sẽ không tồn tại thì có lẽ nhiều chương trình sẽ không vượt qua tên chương trình.
rudimeier

OP đang hỏi một cách hiệu quả "TẠI SAO các tiêu chuẩn POSIX và C nói để làm điều này?" Cấp từ ngữ là ở mức độ trừu tượng, nhưng nó có vẻ rõ ràng. Thực tế, cách duy nhất để biết là hỏi người khởi tạo.
user2338816

21

Ngoài các chương trình thay đổi hành vi của họ tùy thuộc vào cách họ được gọi, tôi thấy argv[0]hữu ích trong việc in sử dụng một chương trình, như vậy:

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

Điều này khiến thông báo sử dụng luôn sử dụng tên mà thông qua đó được gọi. Nếu chương trình được đổi tên, thông báo sử dụng của nó sẽ thay đổi theo. Nó thậm chí bao gồm tên đường dẫn mà nó được gọi với:

# 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]

Đó là một liên lạc tốt đẹp, đặc biệt là đối với các công cụ / tập lệnh có mục đích đặc biệt nhỏ có thể sống ở mọi nơi.

Điều này dường như cũng phổ biến trong các công cụ GNU, xem lsví dụ:

% 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. Tôi sẽ đề nghị như vậy. Điều kỳ lạ là rất nhiều người tập trung vào việc thay đổi hành vi và không đề cập đến có lẽ là cách sử dụng rõ ràng nhất và phổ biến hơn nhiều.
Vee

5

Một người thực hiện chương trình gõ : program_name0 arg1 arg2 arg3 ....

Vì vậy, shell nên chia mã thông báo và mã thông báo đầu tiên đã là tên chương trình. Và BTW vì vậy có cùng các chỉ số ở bên chương trình và trên vỏ.

Tôi nghĩ rằng đây chỉ là một mẹo tiện lợi (ngay từ đầu) và, như bạn thấy trong các câu trả lời khác, nó cũng rất tiện dụng, vì vậy truyền thống này được tiếp tục và đặt làm API.


4

Về cơ bản, argv bao gồm tên chương trình để bạn có thể viết các thông báo lỗi như thế prgm: file: No such file or directorynào sẽ được thực hiện với một cái gì đó như thế này:

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

2

Một ví dụ khác về một ứng dụng của chương trình này là chương trình này, nó tự thay thế bằng ... chính nó, cho đến khi bạn gõ một cái gì đó không 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;
}

Rõ ràng, đây là một ví dụ thú vị, nhưng tôi nghĩ rằng điều này có thể có công dụng thực sự - ví dụ, một nhị phân tự cập nhật, viết lại không gian bộ nhớ của chính nó bằng một phiên bản mới mà chính nó đã tải xuống hoặc thay đổi.

Thí dụ:

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

7 | $

Nguồn, và một số thông tin thêm .


Chúc mừng bạn đã đạt 1000.
G-Man

0

Đường dẫn đến chương trình là argv[0]để chương trình có thể truy xuất các tệp cấu hình, v.v. từ thư mục cài đặt của nó.
Điều này sẽ là không thể nếu không có argv[0].


2
Đó không phải là một lời giải thích đặc biệt tốt - chẳng có lý do gì chúng ta không thể tiêu chuẩn hóa một cái gì đó như (char *path_to_program, char **argv, int argc)ví dụ
moopet

Afaik, hầu hết chương trình kéo cấu hình từ một vị trí chuẩn ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) và một trong hai mất một tham số để thay đổi nó hoặc có một lựa chọn thời gian biên dịch rằng bakes trong một hằng số cho nhị phân.
Xiong Chiamiov

0

ccache hành xử theo cách này để bắt chước các cuộc gọi khác nhau đến các nhị phân của trình biên dịch. ccache là một bộ đệm biên dịch - toàn bộ điểm không bao giờ được biên dịch cùng một mã nguồn hai lần mà thay vào đó trả về mã đối tượng từ bộ đệm nếu có thể.

Từ trang man ccache , "có hai cách để sử dụng ccache. Bạn có thể đặt tiền tố các lệnh biên dịch của mình với ccache hoặc bạn có thể để ccache giả trang thành trình biên dịch bằng cách tạo một liên kết tượng trưng (được đặt tên là trình biên dịch) cho ccache. thuận tiện nhất nếu bạn chỉ muốn dùng thử ccache hoặc muốn sử dụng nó cho một số dự án cụ thể. Phương pháp thứ hai hữu ích nhất khi bạn muốn sử dụng ccache cho tất cả các phần tổng hợp của mình. "

Phương thức symlinks liên quan đến việc chạy các lệnh này:

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 ...

... Hiệu quả của việc này là cho phép ccache ghi lại bất kỳ lệnh nào đã đi đến trình biên dịch, do đó cho phép ccache trả lại tệp đã lưu trong bộ nhớ cache hoặc chuyển lệnh cho trình biên dịch thực tế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.