Czy program może uzyskać liczbę spacji między argumentami wiersza poleceń w POSIX?

23

Powiedz, czy napisałem program z następującym wierszem:

int main(int argc, char** argv)

Teraz wie, jakie argumenty wiersza poleceń są mu przekazywane, sprawdzając treść argv.

Czy program może wykryć, ile spacji między argumentami? Na przykład, gdy wpisuję je w bash:

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

Środowisko to nowoczesny Linux (jak Ubuntu 16.04), ale przypuszczam, że odpowiedź powinna dotyczyć każdego systemu zgodnego z POSIX.

iBug
źródło
22
Tylko z ciekawości, dlaczego twój program miałby to wiedzieć?
nxnev
2
@ nxnev Kiedyś pisałem niektóre programy Windows i wiem, że jest to możliwe, więc zastanawiam się, czy jest coś podobnego w Linuksie (lub Uniksie).
iBug
9
W CP / M niejasno pamiętam, że programy musiały parsować własne linie poleceń - oznaczało to, że każde środowisko wykonawcze C musiało implementować parser powłoki. I wszyscy zrobili to nieco inaczej.
Toby Speight
3
@ iBug Jest, ale musisz zacytować argumenty podczas wywoływania polecenia. Tak dzieje się w przypadku powłok POSIX (i podobnych).
Konrad Rudolph
3
@iBug, ... Windows ma ten sam projekt, o którym Toby wspomniał z CP / M powyżej. UNIX nie robi - z punktu widzenia zwany proces jest, nie ma żadnej linii poleceń zaangażowane w uruchomienie go.
Charles Duffy

Odpowiedzi:

39

Nie ma sensu mówić o „odstępach między argumentami”; to jest koncepcja powłoki.

Zadaniem powłoki jest przyjmowanie całych linii danych wejściowych i formowanie ich w tablice argumentów do uruchamiania poleceń. Może to obejmować parsowanie ciągów cytowanych, rozwijanie zmiennych, symboli wieloznacznych plików i wyrażeń tyldy i więcej. Polecenie jest uruchamiane za pomocą standardowego execwywołania systemowego, które akceptuje wektor ciągów znaków.

Istnieją inne sposoby tworzenia wektora ciągów. Wiele programów rozwidla i wykonuje własne podprocesy za pomocą wcześniej określonych wywołań poleceń - w takim przypadku nigdy nie ma czegoś takiego jak „linia poleceń”. Podobnie, powłoka graficzna (na pulpicie) może rozpocząć proces, gdy użytkownik przeciągnie ikonę pliku i upuści ją w widżecie poleceń - ponownie nie ma linii tekstowej, w której znaki powinny znajdować się między argumentami.

Jeśli chodzi o wywołane polecenie, to, co dzieje się w powłoce lub innym procesie nadrzędnym / prekursorowym, jest prywatne i ukryte - widzimy tylko tablicę ciągów, które standard C określa, które main()mogą zaakceptować.

Toby Speight
źródło
Dobra odpowiedź - ważne jest, aby zwrócić na to uwagę początkującym w Unixie, którzy często zakładają, że jeśli działają, tar cf texts.tar *.txtprogram tar otrzymuje dwa argumenty i musi rozszerzyć sam drugi ( *.txt). Wiele osób nie zdaje sobie sprawy, jak to naprawdę działa, dopóki nie zaczną pisać własnych skryptów / programów obsługujących argumenty.
Laurence Renshaw
58

Ogólnie nie. Analiza wiersza poleceń jest wykonywana przez powłokę, która nie udostępnia nie analizowanego wiersza dla wywoływanego programu. W rzeczywistości twój program może być wykonany z innego programu, który utworzył argv nie przez parsowanie łańcucha, ale przez programową tablicę argumentów.

Hans-Martin Mosner
źródło
9
Możesz wspomnieć execve(2).
iBug
3
Masz rację, jako kiepska wymówka mogę powiedzieć, że obecnie używam telefonu, a przeglądanie stron podręcznika jest trochę nudne :-)
Hans-Martin Mosner
1
To jest odpowiednia sekcja POSIX.
Stephen Kitt
1
@ Hans-MartinMosner: Termux ...? ;-)
DevSolar
9
„ogólnie” miało być zabezpieczeniem przed cytowaniem specjalnego, skomplikowanego przypadku, w którym jest to możliwe - na przykład proces rootowania w suid może być w stanie sprawdzić pamięć powłoki wywołującej i znaleźć nieprzetworzony ciąg wiersza poleceń.
Hans-Martin Mosner
16

Nie, nie jest to możliwe, chyba że spacje są częścią argumentu.

Polecenie uzyskuje dostęp do poszczególnych argumentów z tablicy (w takiej lub innej formie, w zależności od języka programowania), a rzeczywisty wiersz poleceń może zostać zapisany w pliku historii (jeśli zostanie wpisany w interaktywnym wierszu poleceń w powłoce zawierającej pliki historii), ale jest nigdy nie przekazano polecenia w żadnej formie.

Wszystkie polecenia w Uniksie są w końcu wykonywane przez jedną z exec()rodziny funkcji. Pobierają one nazwę polecenia oraz listę lub tablicę argumentów. Żadna z nich nie przyjmuje wiersza poleceń, który został wpisany w wierszu poleceń powłoki. system()Funkcja robi, ale jego ciąg argumentu później wykonywane przez execve(), co znowu bierze tablicę argumentów zamiast ciąg wiersza poleceń.

Kusalananda
źródło
2
@LightnessRacesinOrbit Umieściłem to tam na wypadek, gdyby istniało pewne zamieszanie dotyczące „spacji między argumentami”. Umieszczanie spacji w cudzysłowach między helloi worldto dosłownie spacje między dwoma argumentami.
Kusalananda
5
@Kusalananda - No, no ... Umieszczenie spacji między cytatami helloi worldjest dosłownie dostarczając drugi z trzech argumentów.
Jeremy
@Jeremy Jak powiedziałem, na wypadek, gdyby istniało jakieś zamieszanie co do tego, co rozumie się przez „między argumentami”. Tak, jako drugi argument między pozostałymi dwoma, jeśli chcesz.
Kusalananda
Twoje przykłady były dobre i pouczające.
Jeremy
1
Cóż, chłopaki, przykłady były oczywistym źródłem zamieszania i nieporozumień. Usunąłem je, ponieważ nie dodały wartości odpowiedzi.
Kusalananda
9

Zasadniczo nie jest to możliwe, jak wyjaśniono kilka innych odpowiedzi.

Jednak powłoki uniksowezwykłymi programami (i interpretują wiersz poleceń i globują go, tzn. Rozszerzają polecenie przed wykonaniem fork& execvedla niego). Zobacz to wyjaśnienie dotyczące bashoperacji powłoki . Możesz napisać własną powłokę (lub załatać istniejącą powłokę wolnego oprogramowania , np. GNU bash ) i użyć jej jako powłoki (lub nawet powłoki logowania, patrz passwd (5) i shells (5) ).

Na przykład, możesz mieć swój własny program powłoki, który umieści pełny wiersz poleceń w jakiejś zmiennej środowiskowej (wyobraź sobie MY_COMMAND_LINEna przykład) - lub użyj innego rodzaju komunikacji międzyprocesowej w celu przesłania wiersza poleceń z powłoki do procesu potomnego.

Nie rozumiem, dlaczego chcesz to zrobić, ale możesz kodować powłokę zachowującą się w taki sposób (ale ja tego nie zalecam).

BTW, program może zostać uruchomiony przez jakiś program, który nie jest powłoką (ale który wykonuje fork (2), a następnie wykonuje (2) lub po prostu execveuruchamia program w bieżącym procesie). W takim przypadku w ogóle nie ma wiersza polecenia, a program można uruchomić bez polecenia ...

Zauważ, że możesz mieć jakiś (specjalistyczny) system Linux bez zainstalowanej powłoki. To dziwne i niezwykłe, ale możliwe. Będziesz wtedy trzeba napisać specjalizuje startowy programu począwszy innych programów w miarę potrzeb - bez użycia jakichkolwiek skorupę ale wykonując fork& execvewywołań systemowych.

Przeczytaj także Systemy operacyjne: trzy proste elementy i nie zapominaj, że execvepraktycznie zawsze jest to wywołanie systemowe (w Linuksie są wymienione w syscalls (2) , patrz także wprowadzenie (2) ), które ponownie inicjują wirtualną przestrzeń adresową (i niektóre inne rzeczy) procesu, który to robi.

Basile Starynkevitch
źródło
To najlepsza odpowiedź. Zakładam (bez szukania tego), że argv[0] nazwa programu i pozostałe elementy argumentów są specyfikacjami POSIX i nie można ich zmienić. argv[-1]Zakładam, że środowisko wykonawcze może określać dla wiersza poleceń, ...
Peter - Przywróć Monikę
Nie, nie mógł. Przeczytaj uważnie execvedokumentację. Nie możesz używać argv[-1], używanie jest niezdefiniowane.
Basile Starynkevitch,
Tak, dobra uwaga (również wskazówka, że ​​mamy wywołanie systemowe) - pomysł jest nieco wymyślony. Wszystkie trzy składniki środowiska wykonawczego (shell, stdlib i system operacyjny) muszą współpracować. Powłoka musi wywołać specjalną execvepluscmdfunkcję inną niż POSIX z dodatkowym parametrem (lub konwencją argv), syscall konstruuje wektor argumentu dla main, który zawiera wskaźnik do linii poleceń przed wskaźnikiem do nazwy programu, a następnie przekazuje adres wskaźnika do nazwy programu, jak argvpodczas wywoływania programu main...
Peter - Przywróć Monikę
Nie trzeba ponownie pisać powłoki, wystarczy użyć cudzysłowów. Ta funkcja była dostępna w skorupie bourn sh. Więc nie jest nowy.
ctrl-alt-delor
Używanie cudzysłowów wymaga zmiany wiersza poleceń. I OP tego nie chce
Basile Starynkevitch
3

Zawsze możesz powiedzieć swojej powłoce, aby poinformowała aplikacje, jaki kod powłoki prowadzi do ich wykonania. Na przykład, zshprzekazując te informacje do $SHELL_CODEzmiennej środowiskowej za pomocą preexec()haka ( printenvużytego jako przykład, którego użyłbyś getenv("SHELL_CODE")w swoim programie):

$ 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

Wszystkie te byłyby wykonywane printenvjako:

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

Zezwolenie printenvna pobranie kodu zsh, który prowadzi do wykonania printenvtych argumentów. To, co chciałbyś zrobić z tymi informacjami, nie jest dla mnie jasne.

Dzięki bash, funkcja najbliższa do zsh's preexec()używałaby jej $BASH_COMMANDw DEBUGpułapce, ale zauważ, że bashrobi pewien poziom przepisywania w tym (a w szczególności refaktoryzuje niektóre białe spacje używane jako separator) i to jest stosowane do każdego polecenia (no, niektóre) uruchom, a nie całą linię poleceń wprowadzoną w wierszu poleceń (zobacz także functraceopcję).

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

Zobacz, jak niektóre spacje, które są ogranicznikami w składni języka powłoki, zostały ściśnięte do 1 i jak nie pełna linia poleceń nie zawsze jest przekazywana do polecenia. Prawdopodobnie nie jest to przydatne w twoim przypadku.

Pamiętaj, że nie radziłbym robić tego rodzaju rzeczy, ponieważ potencjalnie wyciekasz poufne informacje do każdego polecenia, jak w:

echo very_secret | wc -c | untrustedcmd

wyciekłby ten sekret do obu wci untrustedcmd.

Oczywiście, możesz zrobić coś takiego dla innych języków niż shell. Na przykład w C można użyć makr eksportujących kod C, który wykonuje polecenie do środowiska:

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

Przykład:

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

Zobacz, jak niektóre miejsca zostały skondensowane przez preprocesor C, jak w przypadku bash. W większości, jeśli nie we wszystkich językach, ilość miejsca używanego w separatorach nie robi różnicy, więc nie jest zaskakujące, że kompilator / interpreter korzysta tutaj z pewnej swobody.

Stéphane Chazelas
źródło
Kiedy testowałem to, BASH_COMMANDnie zawierałem oryginalnych argumentów oddzielających białe znaki, więc nie było to przydatne w przypadku dosłownego żądania OP. Czy ta odpowiedź zawiera jakąkolwiek demonstrację dla tego konkretnego przypadku użycia?
Charles Duffy
@CharlesDuffy, chciałem tylko wskazać najbliższy odpowiednik preexec () zsh w bash (ponieważ jest to powłoka, o której mówił OP) i wskazać, że nie można go użyć w tym konkretnym przypadku użycia, ale zgadzam się, że nie był bardzo czyste. Zobacz edycję. Ta odpowiedź ma być bardziej ogólna na temat przekazywania kodu źródłowego (tutaj w zsh / bash / C), który spowodował wykonanie wykonywanej komendy (nie jest to coś przydatnego, ale mam nadzieję, że to zrobi, a zwłaszcza na przykładach
pokazuję
0

Dodam tylko to, czego brakuje w pozostałych odpowiedziach.

Nie

Zobacz inne odpowiedzi

Może w pewnym sensie

W programie nie można nic zrobić, ale można uruchomić coś w powłoce po uruchomieniu programu.

Musisz użyć cudzysłowów. Więc zamiast

./myprog      aaa      bbb

musisz zrobić jedną z nich

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

Spowoduje to przekazanie do programu pojedynczego argumentu ze wszystkimi spacjami. Istnieje różnica między nimi, druga jest dosłowna, dokładnie tak, jak się wydaje (oprócz tego, że 'musi być wpisana jako \'). Pierwszy interpretuje niektóre znaki, ale dzieli się na kilka argumentów. Aby uzyskać więcej informacji, zobacz cytowanie powłoki. Więc nie ma potrzeby przepisywania powłoki, projektanci powłok już o tym pomyśleli. Ponieważ jednak jest to teraz jeden argument, będziesz musiał wykonać więcej przekazywania w programie.

Opcja 2

Przekaż dane przez stdin. Jest to normalny sposób na pobranie dużej ilości danych do polecenia. na przykład

./myprog << EOF
    aaa      bbb
EOF

lub

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

(Kursywa jest wyjściem programu)

ctrl-alt-delor
źródło
Technicznie Shellcode: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"Wykonuje (zwykle w procesie dziecko) pliku przechowywane ./myprogi przekazuje je dwa argumenty ./myprogi ␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]a argc[1], argcwynosi 2), a także w OP przestrzeń, która oddziela te dwa argumenty nie są przekazywane w sposób do myprog.
Stéphane Chazelas
Ale zmieniasz polecenie, a OP nie chce go zmieniać
Basile Starynkevitch,
@BasileStarynkevitch Po twoim komentarzu ponownie przeczytałem pytanie. Robisz założenie. Nigdzie PO nie mówi, że nie chce zmieniać sposobu uruchamiania programu. Może to prawda, ale nie mieli nic do powiedzenia na ten temat. Dlatego ta odpowiedź może być tym, czego potrzebują.
ctrl-alt-delor
OP pyta wprost o spacje między argumentami, a nie o pojedynczy argument zawierający spacje
Basile Starynkevitch,