Mengapa argv menyertakan nama program?

106

Program Unix / Linux tipikal menerima input baris perintah sebagai jumlah argumen ( int argc) dan vektor argumen ( char *argv[]). Elemen pertama argvadalah nama program - diikuti oleh argumen aktual.

Mengapa nama program diteruskan ke executable sebagai argumen? Apakah ada contoh program menggunakan nama mereka sendiri (mungkin semacam execsituasi)?

Shrikant Giridhar
sumber
6
seperti mv dan cp?
Archemar
9
Di Debian shadalah symlink ke dash. Mereka berperilaku berbeda, ketika dipanggil sebagai shatau sebagaidash
Motte001
21
@AlexejMagura Jika Anda menggunakan sesuatu seperti busybox(umum pada disk-penyelamatan dan semacamnya), maka hampir semuanya (cp, mv, rm, ls, ...) adalah tautan simbolis ke busybox.
Baard Kopperud
11
Saya menemukan ini benar-benar sulit untuk mengabaikan, jadi saya akan mengatakannya: Anda mungkin berarti "GNU" program ( gcc, bash, gunzip, sebagian besar sisa dari OS ...), Linux hanya kernel.
wizzwizz4
10
@ wizzwizz4 Ada apa dengan "Program Unix / Linux Khas"? Saya membacanya seperti "Program khas berjalan di Unix / Linux". Itu jauh lebih baik daripada pembatasan Anda untuk program GNU tertentu. Dennis Ritchie tentu saja tidak menggunakan program GNU. BTW, kernel Hurd adalah contoh dari program GNU yang tidak memiliki fungsi utama ...
rudimeier

Jawaban:

122

Untuk mulai dengan, perhatikan bahwa argv[0]tidak harus nama program. Ini adalah apa pemanggil menempatkan ke argv[0]dari execvesystem call (misalnya lihat pertanyaan ini pada Stack Overflow ). (Semua varian lain execbukan pemanggilan sistem tetapi antarmuka untuk execve.)

Misalkan, misalnya, yang berikut (menggunakan execl):

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

/var/tmp/mybackdooradalah apa yang dieksekusi tetapi argv[0]diatur ke top, dan ini adalah apa psyang topakan ditampilkan. Lihat jawaban ini di U&L SE untuk informasi lebih lanjut tentang ini.

Mengesampingkan semua ini: Sebelum munculnya filesystem mewah seperti /proc, argv[0]adalah satu-satunya cara bagi proses untuk belajar tentang namanya sendiri. Apa gunanya itu?

  • Beberapa program menyesuaikan perilaku mereka tergantung pada nama panggilan mereka (biasanya dengan tautan simbolis atau keras, misalnya utilitas BusyBox ; beberapa contoh lainnya disediakan dalam jawaban lain untuk pertanyaan ini).
  • Selain itu, layanan, daemon, dan program lain yang masuk melalui syslog sering menambahkan nama mereka ke entri log; tanpa ini, pelacakan acara akan menjadi tidak mungkin.
countermode
sumber
18
Contoh dari program tersebut adalah bunzip2, bzcatdan bzip2, untuk yang pertama adalah symlink ke yang ketiga.
Ruslan
5
@Ruslan Menariknya zcatbukan symlink. Mereka tampaknya menghindari kelemahan dari teknik ini menggunakan skrip shell sebagai gantinya. Tetapi mereka gagal mencetak --helpkeluaran lengkap karena seseorang yang menambahkan opsi ke gzip lupa untuk memelihara zcat juga.
rudimeier
1
Sepanjang yang saya ingat, standar pengkodean GNU telah mengecilkan penggunaan argv [0] untuk mengubah perilaku program ( bagian "Standar untuk Antarmuka Umumnya" dalam versi saat ini ). gunzipadalah pengecualian historis.
19
busybox adalah contoh bagus lainnya. Ini dapat dipanggil oleh 308 nama yang berbeda untuk memanggil perintah yang berbeda: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz
2
Banyak, banyak lagi program yang juga menyuntikkan mereka argv[0]dalam penggunaan / bantuan keluaran mereka daripada meng-coding nama mereka. Beberapa secara penuh, beberapa hanya nama samaran.
Spectra
62

Banyak:

  • Bash berjalan dalam modus POSIX saat argv[0]ini sh. Ini berjalan sebagai shell login ketika argv[0]dimulai dengan -.
  • Vim berperilaku berbeda ketika dijalankan sebagai vi, view, evim, eview, ex, vimdiff, dll
  • Busybox, seperti yang sudah disebutkan.
  • Dalam sistem dengan systemd sebagai init, shutdown, reboot, dll symlink untuksystemctl .
  • dan seterusnya.
muru
sumber
7
Yang lain adalah sendmaildan mail. Setiap MTA unix tunggal hadir dengan symlink untuk dua perintah tersebut, dan dirancang untuk meniru perilaku asli ketika dipanggil, yang berarti bahwa setiap program unix yang perlu mengirim email tahu persis bagaimana mereka dapat melakukannya.
Shadur
4
kasus umum lainnya: testdan [: ketika Anda memanggil yang pertama, ia menangani kesalahan jika argumen terakhir adalah ]. (pada stabil Debian aktual, perintah ini adalah dua program yang berbeda, tetapi versi sebelumnya dan MacO masih menggunakan program yang sama). Dan tex, latexdan seterusnya: binernya sama, tetapi melihat bagaimana namanya, ia memilih file konfigurasi yang tepat . initserupa.
Giacomo Catenazzi
4
Terkait, [menganggapnya sebagai kesalahan jika argumen terakhir tidak ] .
chepner
Saya kira ini menjawab pertanyaan kedua, tetapi bukan yang pertama. Saya sangat meragukan beberapa perancang OS duduk dan berkata, “Hei, akan lebih baik jika saya memiliki program yang sama melakukan hal-hal yang berbeda hanya berdasarkan nama yang dapat dieksekusi. Saya kira saya akan memasukkan nama dalam susunan argumennya. «
Joey
@ Joey Ya, kata-katanya dimaksudkan untuk menyampaikan bahwa (T: "Apakah ada ...?" A: "Banyak: ...")
muru
34

Secara historis, argvhanyalah array pointer ke "kata-kata" dari commandline, jadi masuk akal untuk memulai dengan "kata" pertama, yang kebetulan merupakan nama program.

Dan ada beberapa program yang berperilaku berbeda sesuai dengan nama yang digunakan untuk memanggil mereka, jadi Anda bisa membuat tautan berbeda ke mereka dan mendapatkan "perintah" yang berbeda. Contoh paling ekstrem yang bisa saya pikirkan adalah busybox , yang bertindak seperti beberapa lusin "perintah" yang berbeda tergantung bagaimana namanya .

Sunting : Referensi untuk Unix edisi 1, seperti yang diminta

Orang dapat melihat misalnya dari fungsi utamacc itu argcdan argvsudah digunakan. The shell salinan argumen ke parbufdalam newargbagian dari loop, sementara memperlakukan perintah itu sendiri dalam cara yang sama seperti argumen. (Tentu saja, nanti hanya menjalankan argumen pertama, yang merupakan nama perintah). Sepertinya execvdan kerabat tidak ada saat itu.

dirkt
sumber
1
tolong tambahkan referensi yang mendukung ini.
lesmana
Dari skimming cepat, execmengambil nama dari perintah untuk mengeksekusi dan array nol-dihentikan pointer char (terbaik dilihat di minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , di mana execdibutuhkan referensi ke label 2 dan label 1, dan pada label 2:muncul etc/init\0, dan pada label 1:muncul referensi ke label 2, dan berakhir nol), yang pada dasarnya adalah apa yang execvedikurangi hari ini envp.
ninjalj
1
execvdan execltelah ada "selamanya" (yaitu, sejak awal hingga pertengahan 1970-an) - execvadalah panggilan sistem dan execlfungsi perpustakaan yang menyebutnya.   execvetidak ada karena lingkungan tidak ada. Anggota keluarga lainnya ditambahkan kemudian.
G-Man
@ G-Man Bisakah Anda mengarahkan saya ke execvsumber v1 yang saya tautkan? Hanya penasaran.
dirkt
22

Gunakan kasing:

Anda dapat menggunakan nama program untuk mengubah perilaku program .

Misalnya Anda bisa membuat beberapa symlink ke biner yang sebenarnya.

Salah satu contoh terkenal di mana teknik ini digunakan adalah proyek busybox yang menginstal hanya satu biner tunggal dan banyak symlink ke sana. (ls, cp, mv, dll). Mereka melakukannya untuk menghemat ruang penyimpanan karena target mereka adalah perangkat tertanam kecil.

Ini juga digunakan di setarchdari 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

Di sini mereka menggunakan teknik ini pada dasarnya untuk menghindari banyak file sumber duplikat atau hanya agar sumbernya lebih mudah dibaca.

Kasus penggunaan lain adalah program yang perlu memuat beberapa modul atau data saat runtime. Memiliki jalur program membuat Anda dapat memuat modul dari jalur relatif ke lokasi program .

Apalagi banyak program mencetak pesan kesalahan termasuk nama program .

Mengapa :

  1. Karena itu adalah konvensi POSIX ( man 3p execve):

argv adalah larik string argumen yang diteruskan ke program baru. Secara konvensi, string pertama harus berisi nama file yang terkait dengan file yang dieksekusi.

  1. Ini standar C (setidaknya C99 dan C11):

Jika nilai argc lebih besar dari nol, string yang ditunjukkan oleh argv [0] mewakili nama program; argv [0] [0] akan menjadi karakter nol jika nama program tidak tersedia dari lingkungan host.

Perhatikan Standar C mengatakan "nama program" bukan "nama file".

rudimeier
sumber
3
Bukankah ini pecah jika Anda mencapai symlink dari symlink lain?
Mehrdad
3
@Mehrdad, Ya itu sisi buruknya dan bisa membingungkan pengguna.
rudimeier
@rudimeier: Item 'Kenapa' Anda tidak benar-benar alasan, mereka hanya "homunculus", yaitu hanya menanyakan pertanyaan mengapa standar mengharuskan ini terjadi.
einpoklum
Pertanyaan @einpoklum OP adalah: Mengapa nama program diteruskan ke executable? Saya menjawab: Karena POSIX dan standar C memberitahu kita untuk melakukannya. Bagaimana menurut Anda itu sebenarnya bukan alasan ? Jika dokumen yang saya kutip tidak akan ada maka mungkin banyak program tidak akan memberikan nama program.
rudimeier
OP secara efektif bertanya "MENGAPA POSIX dan standar C mengatakan untuk melakukan ini?" Memang kata-katanya pada tingkat abstrak, tetapi tampaknya jelas. Secara realistis, satu-satunya cara untuk mengetahui adalah dengan bertanya kepada pencetusnya.
user2338816
21

Selain program yang mengubah perilaku mereka tergantung pada bagaimana mereka dipanggil, saya merasa argv[0]berguna dalam mencetak penggunaan suatu program, seperti:

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

Ini menyebabkan pesan penggunaan selalu menggunakan nama yang digunakan untuk memanggilnya. Jika program diubah namanya, pesan penggunaannya akan berubah. Bahkan termasuk nama jalur yang dipanggil dengan:

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

Ini adalah sentuhan yang bagus, terutama untuk alat / skrip bertujuan kecil yang mungkin tinggal di mana-mana.

Ini sepertinya praktik umum dalam alat GNU juga, lihat lsmisalnya:

% 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.
marcelm
sumber
3
+1. Saya akan menyarankan hal yang sama. Aneh bahwa begitu banyak orang fokus pada perubahan perilaku dan gagal menyebutkan mungkin penggunaan yang paling jelas dan jauh lebih luas.
The Vee
5

Satu mengeksekusi program mengetik: program_name0 arg1 arg2 arg3 ....

Jadi shell seharusnya sudah membagi token, dan token pertama sudah menjadi nama program. Dan BTW jadi ada indeks yang sama di sisi program dan di shell.

Saya pikir ini hanya trik yang mudah (pada awalnya), dan, seperti yang Anda lihat di jawaban lain, itu juga sangat berguna, sehingga tradisi ini dilanjutkan dan ditetapkan sebagai API.

Giacomo Catenazzi
sumber
4

Pada dasarnya, argv menyertakan nama program sehingga Anda dapat menulis pesan kesalahan seperti prgm: file: No such file or directory, yang akan diimplementasikan dengan sesuatu seperti ini:

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

Contoh lain dari aplikasi ini adalah program ini, yang menggantikan dirinya dengan ... itu sendiri, sampai Anda mengetik sesuatu yang tidak 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;
}

Jelas, semacam contoh menarik jika dibuat, tapi saya pikir ini mungkin memiliki kegunaan nyata - misalnya, biner memperbarui diri, yang menulis ulang ruang memori sendiri dengan versi baru sendiri yang diunduh atau diubah.

Contoh:

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

7 | $

Sumber, dan beberapa info lainnya .

kucing
sumber
Selamat telah mencapai 1000.
G-Man
0

Path ke program adalah argv[0], sehingga program dapat mengambil file konfigurasi dll dari direktori instalnya.
Ini tidak mungkin tanpa argv[0].

bob cook
sumber
2
Itu bukan penjelasan yang sangat bagus - tidak ada alasan kita tidak bisa melakukan standarisasi pada sesuatu seperti (char *path_to_program, char **argv, int argc)misalnya
moopet
Afaik, sebagian besar program menarik konfigurasi dari lokasi standar ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) dan baik mengambil parameter untuk mengubah atau memiliki pilihan kompilasi yang bakes dalam konstan untuk biner.
Xiong Chiamiov
0

ccache berperilaku seperti ini untuk meniru panggilan berbeda ke kompiler binari. ccache adalah kompilasi cache - seluruh intinya tidak pernah mengkompilasi kode sumber yang sama dua kali tetapi sebaliknya mengembalikan kode objek dari cache jika memungkinkan.

Dari halaman manual ccache , "ada dua cara untuk menggunakan ccache. Anda dapat mengawali perintah kompilasi dengan ccache atau Anda dapat membiarkan ccache menyamar sebagai kompiler dengan membuat tautan simbolis (dinamai sebagai kompiler) ke ccache. Metode pertama lebih nyaman jika Anda hanya ingin mencoba ccache atau ingin menggunakannya untuk beberapa proyek tertentu. Metode kedua paling berguna ketika Anda ingin menggunakan ccache untuk semua kompilasi Anda. "

Metode symlinks melibatkan menjalankan perintah-perintah ini:

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

... efeknya adalah memungkinkan ccache untuk mengambil perintah apa pun yang seharusnya pergi ke kompiler, sehingga memungkinkan ccache untuk mengembalikan file yang di-cache atau meneruskan perintah ke kompiler yang sebenarnya.

Adam J Richardson
sumber