Bir programın POSIX'deki komut satırı argümanları arasında boşluk sayısı olması mümkün mü?


23

Aşağıdaki satırı içeren bir program yazdıysam söyleyin:

int main(int argc, char** argv)

Şimdi hangi komut satırı argümanlarının içeriğini kontrol ederek kendisine iletildiğini biliyor argv.

Program argümanlar arasında kaç boşluk olduğunu tespit edebilir mi? Bunları bash yazarken olduğu gibi:

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

Ortam modern bir Linux'tur (Ubuntu 16.04 gibi), ancak cevabın POSIX uyumlu herhangi bir sistem için geçerli olması gerektiğini düşünüyorum.


22
Sadece merak için programınızın neden bunu bilmesi gerekiyor?
nxnev

2
@nxnev Eskiden bazı Windows programları yazıyordum ve orada mümkün olduğunu biliyorum, bu yüzden Linux (veya Unix) 'te benzer bir şey olup olmadığını merak ediyorum.
iBug,

9
CP / M'de programların kendi komut satırlarını ayrıştırmak zorunda olduğunu açıkça hatırlıyorum - bu her C çalışma zamanının bir kabuk ayrıştırıcı kullanması gerektiği anlamına geliyordu. Ve hepsi bunu biraz farklı yaptı.
Toby Speight

3
@iBug Var, ancak komutu çağırırken argümanları alıntılamanız gerekir. POSIX (ve benzeri) kabuklarında böyle yapılır.
Konrad Rudolph

3
@iBug, ... Windows, Toby'nin yukarıda CP / M'den bahsettiği tasarıma sahip. UNIX bunu yapmaz - denilen sürecin bakış açısıyla orada olduğunu çalıştırmaya dahil hiçbir komut satırı.
Charles Duffy

Yanıtlar:


39

“Argümanlar arasındaki boşluklardan” bahsetmek anlamlı değildir; Bu bir kabuk kavramı.

Bir kabuğun işi bütün girdi satırlarını alıp komutları başlatmak için argüman dizileri haline getirmektir. Bu, alıntılanan dizeleri ayrıştırma, değişkenleri genişletme, joker karakterler ve tilde ifadeleri ve daha fazlasını içerebilir. Komut, execdizgelerin bir vektörünü kabul eden standart bir sistem çağrısı ile başlatılır .

Bir dizi vektör oluşturmanın başka yolları da vardır. Birçok program önceden tanımlanmış komut çağrılarıyla kendi alt işlemlerini gerçekleştirir ve uygular - bu durumda asla "komut satırı" diye bir şey yoktur. Benzer şekilde, bir grafik dosyası (masaüstü) kabuğu, bir kullanıcı bir dosya simgesini sürükleyip bir komut gerecine bıraktığında bir işlemi başlatabilir - yine, "argümanlar" arasında karakterlerin olduğu bir metin satırı yoktur.

Çağrılan komut söz konusu olduğunda, bir kabukta veya başka bir üst / öncü işleminde neler olup bittiği özel ve gizlidir - yalnızca standart C'nin main()kabul edebileceğini belirttiği dizelerin dizisini görürüz .


İyi cevap - bunu, Unix'e yeni başlayanlar için belirtmek önemlidir; çoğu zaman, eğer çalışırlarsa tar cf texts.tar *.txt, tar programının iki argüman aldığını ve ikincisinin ( *.txt) kendisini genişletmesi gerektiğini varsayar . Birçok kişi, kendi yazılarını / argümanlarını ele alan programlarını yazmaya başlayana kadar gerçekten nasıl çalıştığını anlamıyor.
Laurence Renshaw

58

Genel olarak hayır. Komut satırı ayrıştırma, ayrıştırılmamış satırı çağrılan program için kullanılabilir hale getirmeyen kabuk tarafından yapılır. Aslında, programınız bir dize ayrıştırılarak değil, programsal olarak bir argüman dizisi oluşturularak argv'yi oluşturan başka bir programdan çalıştırılabilir.


9
Bahsetmek isteyebilirsin execve(2).
iBug,

3
Bir topal bahane olarak ben şu anda biraz sıkıcı :-) adam sayfaları bir telefon kullanarak ve altına bakıyorum söyleyebiliriz, haklısın
Hans-Martin Mosner

1
Bu POSIX ilgili bölümüdür.
Stephen Kitt

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

9
"genel olarak", mümkün olduğunda özel bir katlanmış durumdan alıntı yapılmasına karşı bir güvenlik önlemi anlamına geliyordu - örneğin, bir suid kök işlemi çağıran kabuğun belleğini inceleyebilir ve ayrıştırılmamış komut satırı dizesini bulabilirdi.
Hans Martin Martin Mosner

16

Hayır, boşluklar bir argümanın parçası olmadığı sürece bu mümkün değildir .

Komut, bağımsız değişkenlere bir diziden (programlama diline bağlı olarak bir biçimde veya başka bir şekilde) erişir ve gerçek komut satırı bir geçmiş dosyasına (eğer etkileşimli bir komut isteminde geçmiş dosyaları olan bir kabukta yazılırsa) kaydedilebilir, ancak hiçbir zaman komuta hiçbir şekilde geçemedi.

Unix'teki tüm komutlar sonunda exec()fonksiyon ailesinden biri tarafından yürütülür . Bunlar komut adını ve bir liste veya argüman dizisini alır. Hiçbiri kabuk isteminde yazılan komut satırını alamaz. system()Fonksiyon yapar, ama onun dize argümanı sonra tarafından yürütülür execve()yine ifade dizi yerine bir komut satırı dizesi aldığı,.


2
@LightnessRacesinOrbit "Argümanlar arasındaki boşluklar" konusunda biraz karışıklık olması durumunda oraya koydum. Arasındaki tırnak içinde boşluk koymak hellove worldbir anlamıyla iki argüman arasındaki boşluklar.
Kusalananda

5
@Kusalananda - Hayır ... arasındaki tırnak içinde boşluk koymak hellove worldbir anlamıyla üç argüman ikincisini besleyen.
Jeremy

Jeremy: Söylediğim gibi, "tartışmalar arasında" ile ne kastedildiği konusunda herhangi bir karışıklık olması durumunda. Evet, eğer diğer ikisi arasında ikinci bir tartışma olarak .
Kusalananda

Örneklerin iyi ve öğreticiydi.
Jeremy

1
Beyler, örnekler açık bir karışıklık ve yanlış anlama kaynağıydı. Yanıt değerini eklemediği için onları sildim.
Kusalananda

9

Genel olarak, açıklanan diğer birkaç cevap gibi, mümkün değildir.

Ancak, Unix kabukları olan sıradan programlar (ve komut satırını yorumlama ve edilir globbing yani bunu genişleyen yapmadan önce komutunu fork& execvebunun için). Kabuk işlemleri hakkında bu açıklamayabash bakınız . Sen olabilir Kendi kabuğunu yazmak (veya bazı mevcut yama olabilir özgür yazılım kabuğu, örneğin GNU bash ) ve kabuk olarak kullanmak (hatta giriş kabuğu, bkz : 5 (passwd) & kabukları (5) ).

Örneğin, aklınıza gelebilecek sizin kendi kabuk programı bazı ortam değişkeni tam komut satırı koymak (hayal MY_COMMAND_LINEörneğin) -veya başka türlü kullanmak süreçler arası iletişim kabuğundan çocuk takviye suyunun için komut satırını iletmek için.

Bunu neden yapmak istediğinizi anlamıyorum, ancak bu şekilde davranan bir kabuğunu kodlayabilirsiniz (ancak bunu yapmamanızı tavsiye ederim).

BTW, bir kabuk olmayan bir program tarafından başlatılabilir (ancak çatal (2) daha sonra çalıştırır (2) veya sadece bir execveprogramın mevcut işlemine başlayabilir). Bu durumda hiçbir komut satırı yoktur ve programınız bir komut olmadan başlatılabilir ...

Herhangi bir kabuk kurulmadan bazı (uzmanlaşmış) bir Linux sisteminizin olabileceğine dikkat edin. Bu garip ve sıradışı, ama mümkün. Daha sonra gerektiğinde diğer programları başlatan özel bir başlangıç programı yazmanız gerekecek - herhangi bir kabuk kullanmadan ancak fork& execvesistem çağrıları yaparak .

Ayrıca bkz. İşletim Sistemleri: Üç kolay parça ve unutma ki execve, pratik olarak her zaman bir sistem çağrısıdır (Linux'ta, sanal sistemler aralığını yeniden başlatan (ve diğer bazılarını da giriş (2) ) olarak listelenmiştir ) şeyler) sürecin yapıyor.


Bu en iyi cevap. argv[0] Program adı ve argümanlar için kalan öğelerin POSIX özellikleri olduğunu ve değiştirilemeyeceğini (yukarı bakmadan) kabul ediyorum . Çalışma zamanı ortamı argv[-1]komut satırı için belirleyebilir , sanırım ...
Peter - Monica

Hayır, yapamadı. Daha dikkatli bir şekilde execvebelgeleri okuyun . Sen kullanamaz argv[-1]bunu kullanmak tanımsız davranıştır.
Basile Starynkevitch

Evet, iyi nokta (aynı zamanda bir sistem çağrımızın olduğu ipucu) - fikir biraz tartışmalı. Çalışma zamanının üç bileşeninin de (kabuk, stdlib ve işletim sistemi) birlikte çalışması gerekir. Kabuğun execvepluscmd, fazladan bir parametreyle (veya argv konvansiyonu) özel bir POSIX dışı işlev çağırması gerekir , sistem çağrısı, göstergeden önce program ismine göstericiden komut satırına bir işaretçi içeren bir argüman vektörü oluşturur ve ardından adresi iletir. Programın isminin argvişaretini kullanarak programın main...
Peter - Monica

Kabuğunu yeniden yazmaya gerek yok, sadece tırnak işaretlerini kullanın. Bu özellik, burk kabuğundan elde edilebilirdi sh. Yani yeni değil.
ctrl-alt-delor

Tırnak kullanmak , komut satırını değiştirmenizi gerektirir . Ve OP bunu istemiyor
Basile Starynkevitch

3

Uygulamaya, kabuk kodunun yürütülmesine hangi kabuk kodun yol açtığını söylemesini her zaman söyleyebilirsiniz. Örneğin, zshbu bilgileri kancayı $SHELL_CODEkullanarak ortam değişkenine geçirerek preexec()( printenvörnek olarak kullanılır getenv("SHELL_CODE"), programınızda kullanırsınız):

$ 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

Bütün printenvbunlar şu şekilde yürütülür :

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

Bu argümanlarla printenvyürütülmesine yol açan zsh kodunun alınmasına izin verilmesi printenv. Bu bilgi ile ne yapmak istediğinizi bana açık değildir.

İle bashözelliğiyle en yakın, zshs' preexec()onun kullanarak olacağını $BASH_COMMANDbir de DEBUGtuzak, ancak not bash(iyi bazı) (özellikle refactors de ayırıcı olarak kullanılan boşluk bazı ve) ve bu en her uygulanmış ki yeniden belli bir düzeyde yapar komut komut satırında girilen komut satırının tamamını değil (aynı zamanda functraceseçeneğe bakın).

$ 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')

Kabuk dili sözdiziminde sınırlayıcı olan boşlukların bazılarının 1'e nasıl sıkıştırıldığını ve tam komut satırının her zaman komuta alınmadığını görün. Muhtemelen davanda işe yaramaz.

Her türlü komutta aşağıdaki gibi hassas bilgileri potansiyel olarak sızdırıyorsanız, bu tür bir işlem yapmamayacağımı unutmayın:

echo very_secret | wc -c | untrustedcmd

hem bu sırrı kaçırır ama wcve untrustedcmd.

Tabii ki, bu tür bir şeyi kabuktan başka diller için de yapabilirsin. Örneğin, C’de, bir komutu çalıştıran C kodunu çevreye veren bazı makroları kullanabilirsiniz:

#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;
}

Örnek:

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

Bazı durumlarda, bash durumundaki gibi C ön işlemcisi tarafından nasıl yoğunlaştırıldığını görün. Çoğunlukla tüm diller değilse, sınırlayıcılarda kullanılan alan miktarı bir fark yaratmaz, bu nedenle derleyicinin / tercümanın burada onlarla biraz özgürlük kazanması şaşırtıcı değildir.


Bunu test ederken, BASH_COMMANDargümanları ayıran orijinal boşluk içermiyordu, bu yüzden OP'nin hazır talebi için bu mümkün değildi. Bu cevap, söz konusu kullanım durumu için herhangi bir şekilde herhangi bir gösterimi içeriyor mu?
Charles Duffy

@CharlesDuffy, zh'nin preexec'in en yakın eşdeğerini bash cinsinden belirtmek istedim (OP'nin başvurduğu kabuk olduğu gibi) ve bu belirli kullanım durumu için kullanılamadığını belirtmiştim, çok açık. Düzenlemeye bakınız. Bu cevabın, yürütmenin yürütülmekte olan komuta neden olan (burada zsh / bash / C cinsinden) kaynak kodunun (burada zsh / bash / C cinsinden) nasıl aktarılacağı hakkında daha genel olması amaçlanmıştır. örneklerle çok faydalı olmadığını gösteriyorum)
Stéphane Chazelas

0

Sadece diğer cevaplarda eksik olanı ekleyeceğim.

Yok hayır

Diğer cevaplara bakın

Belki, bir nevi

Programda yapılabilecek hiçbir şey yoktur, ancak programı çalıştırdığınızda kabukta yapılabilecek bir şey vardır.

Tırnak kullanmanız gerekir. Yani yerine

./myprog      aaa      bbb

bunlardan birini yapmalısın

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

Bu, tüm boşluklarla birlikte programa tek bir argüman iletecektir. İkisi arasında bir fark vardır, ikincisi değişmezdir, göründüğü gibi tam olarak dizedir (bunun dışında 'yazılması gerekir \'). İlki bazı karakterleri yorumlayacak, ancak birkaç argümana bölünecektir. Daha fazla bilgi için kabuk teklifine bakınız. Bu yüzden kabuğu yeniden yazmaya gerek yok, kabuk tasarımcıları zaten bunu düşünmüş. Ancak şimdi tek bir argüman olduğu için programdan daha fazla geçiş yapmanız gerekecek.

seçenek 2

Verileri stdin ile iletin. Bu, büyük miktarda veriyi bir komuta almak için normal yoldur. Örneğin

./myprog << EOF
    aaa      bbb
EOF

veya

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

(Italics programın çıktısıdır)


Teknik olarak, kabuk kodu: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"(genellikle alt sürecin) yürütür dosyası içinde saklanır ./myprogve geçtiği iki bağımsız değişkeni: ./myprogve ␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]ve argc[1], argc2 olmak üzere) ve OP en gibi, bu iki bağımsız olan boşluklar herhangi bir şekilde geçmedi için myprog.
Stéphane Chazelas

Ama emri değiştiriyorsun, ve OP de değişmek istemiyor
Basile Starynkevitch

@BasileStarynkevitch Yorumunuzu takiben soruyu tekrar okudum. Bir varsayımda bulunuyorsun. OP hiçbir yerde programın çalışma şeklini değiştirmek istemediklerini söylemez. Belki bu doğrudur, ama söyleyecek hiçbir şeyleri yoktu. Dolayısıyla bu cevap ihtiyaç duydukları şey olabilir.
ctrl-alt-delor

OP sormak açıkça Alan konusunda arasındaki , argümanlar değil yaklaşık bir tek argüman içeren boşluklar
Basile Starynkevitch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.