È possibile per un programma ottenere il numero di spazi tra gli argomenti della riga di comando in POSIX?


23

Di 'se ho scritto un programma con la seguente riga:

int main(int argc, char** argv)

Ora sa quali argomenti della riga di comando gli vengono passati controllando il contenuto di argv.

Il programma può rilevare quanti spazi tra gli argomenti? Come quando li scrivo in bash:

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

L'ambiente è un moderno Linux (come Ubuntu 16.04), ma suppongo che la risposta dovrebbe applicarsi a tutti i sistemi compatibili con POSIX.


22
Solo per curiosità, perché il tuo programma dovrebbe saperlo?
nxnev,

2
@nxnev Scrivevo alcuni programmi Windows e so che è possibile lì, quindi mi chiedo se ci sia qualcosa di simile in Linux (o Unix).
iBug

9
Ricordo vagamente in CP / M che i programmi dovevano analizzare le proprie righe di comando - questo significava che ogni runtime C doveva implementare un parser di shell. E lo hanno fatto tutti in modo leggermente diverso.
Toby Speight,

3
@iBug C'è, ma è necessario citare gli argomenti quando si richiama il comando. È così che viene fatto su shell POSIX (e simili).
Konrad Rudolph,

3
@iBug, ... Windows ha lo stesso design che Toby menziona dal CP / M sopra. UNIX non lo fa - dal punto di vista del processo chiamato, non esiste alcuna riga di comando per eseguirlo.
Charles Duffy,

Risposte:


39

Non è significativo parlare di "spazi tra argomenti"; questo è un concetto di shell.

Il lavoro di una shell è prendere intere righe di input e formarle in matrici di argomenti con cui avviare i comandi. Ciò può comportare l'analisi di stringhe tra virgolette, l'espansione di variabili, i caratteri jolly dei file e le espressioni tilde e altro ancora. Il comando viene avviato con una execchiamata di sistema standard , che accetta un vettore di stringhe.

Esistono altri modi per creare un vettore di stringhe. Molti programmi eseguono fork ed eseguono i propri processi secondari con invocazioni di comandi predeterminate, nel qual caso non esiste mai una "riga di comando". Allo stesso modo, una shell grafica (desktop) potrebbe avviare un processo quando un utente trascina un'icona di file e la rilascia su un widget di comando - di nuovo, non c'è una riga testuale per avere caratteri "tra" argomenti.

Per quanto riguarda il comando invocato, ciò che accade in una shell o in un altro processo genitore / precursore è privato e nascosto - vediamo solo l'array di stringhe che lo standard C specifica che main() può accettare.


Buona risposta - è importante evidenziarlo per i neofiti di Unix, che spesso assumono che, se eseguito, tar cf texts.tar *.txtil programma tar ottiene due argomenti e deve espandere il secondo ( *.txt) stesso. Molte persone non si rendono conto di come funziona fino a quando non iniziano a scrivere i propri script / programmi che gestiscono gli argomenti.
Laurence Renshaw,

58

In generale, no. L'analisi della riga di comando viene eseguita dalla shell che non rende la riga non analizzata disponibile per il programma chiamato. In effetti, il tuo programma potrebbe essere eseguito da un altro programma che ha creato l'argv non analizzando una stringa ma costruendo un array di argomenti a livello di programmazione.


9
Potresti voler menzionare execve(2).
iBug

3
Hai ragione, come scusa zoppicante posso dire che attualmente sto usando un telefono e cercare pagine man è un po 'noioso :-)
Hans-Martin Mosner

1
Questa è la sezione pertinente di POSIX.
Stephen Kitt,

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

9
"in generale" era inteso come una protezione contro la citazione di un caso contorto speciale dove è possibile - per esempio, un processo suid root potrebbe essere in grado di ispezionare la memoria della shell chiamante e trovare la stringa della riga di comando non analizzata.
Hans-Martin Mosner,

16

No, questo non è possibile, a meno che gli spazi non facciano parte di un argomento.

Il comando accede ai singoli argomenti da un array (in una forma o nell'altra a seconda del linguaggio di programmazione) e la riga di comando effettiva può essere salvata in un file di cronologia (se digitata a un prompt interattivo in una shell che ha file di cronologia), ma è mai passato al comando in nessuna forma.

Tutti i comandi su Unix sono infine eseguiti da una delle exec()famiglie di funzioni. Questi prendono il nome del comando e un elenco o una matrice di argomenti. Nessuno di loro accetta una riga di comando come digitata al prompt della shell. La system()funzione lo fa, ma il suo argomento stringa viene successivamente eseguito da execve(), che, di nuovo, accetta una matrice di argomenti anziché una stringa della riga di comando.


2
@LightnessRacesinOrbit L'ho messo lì nel caso in cui ci fosse un po 'di confusione su "spazi tra argomenti". Mettere spazi tra virgolette tra helloed worldè letteralmente spazi tra i due argomenti.
Kusalananda

5
@Kusalananda - Beh, no ... Mettere spazi tra virgolette tra helloed worldè letteralmente fornendo il secondo dei tre argomenti.
Jeremy,

@Jeremy Come ho detto, nel caso ci fosse qualche confusione su cosa si intendesse per "tra gli argomenti". Sì, se vuoi , come secondo argomento tra gli altri due.
Kusalananda

I tuoi esempi sono stati eccellenti e istruttivi.
Jeremy,

1
Bene, ragazzi, gli esempi erano un'ovvia fonte di confusione e incomprensioni. Li ho cancellati perché non sono stati aggiunti al valore della risposta.
Kusalananda

9

In generale, non è possibile, come spiegato in molte altre risposte.

Tuttavia, le shell Unix sono programmi ordinari (e stanno interpretando la riga di comando e la stanno traballando , cioè espandendo il comando prima di farlo forke execveper esso). Vedi questa spiegazione sulle bashoperazioni della shell . Potresti scrivere la tua shell (o puoi applicare patch a una shell software esistente , ad esempio GNU bash ) e usarla come shell (o anche come shell di accesso, vedi passwd (5) e shells (5) ).

Ad esempio, è possibile che il proprio programma shell inserisca l'intera riga di comando in alcune variabili di ambiente ( MY_COMMAND_LINEad esempio, immaginiamo ) oppure utilizzare qualsiasi altro tipo di comunicazione tra processi per trasmettere la riga di comando dalla shell al processo figlio-.

Non capisco perché vorresti farlo, ma potresti codificare una shell che si comporta in questo modo (ma consiglio di non farlo).

A proposito, un programma potrebbe essere avviato da un programma che non è una shell (ma che fa fork (2) quindi esegue (2) , o semplicemente unexecve per avviare un programma nel suo processo corrente). In tal caso non esiste alcuna riga di comando e il programma potrebbe essere avviato senza un comando ...

Nota che potresti avere un sistema Linux (specializzato) senza alcuna shell installata. Questo è strano e insolito, ma possibile. Dovrai quindi scrivere un programma init specializzato avviando altri programmi secondo necessità, senza usare alcuna shell ma facendo forke execvechiamate di sistema.

Leggi anche Sistemi operativi: tre semplici passaggi e non dimenticare che execveè praticamente sempre una chiamata di sistema (su Linux, sono elencati in syscalls (2) , vedi anche intro (2) ) che reinizializzano lo spazio di indirizzi virtuale (e alcuni altri cose) del processo che lo sta facendo.


Questa è la risposta migliore Presumo (senza averlo cercato) che argv[0] per il nome del programma e gli elementi rimanenti per gli argomenti sono specifiche POSIX e non possono essere modificate. Un ambiente di runtime potrebbe specificare argv[-1]per la riga di comando, suppongo ...
Peter - Ripristina Monica il

No, non potrebbe. Leggi più attentamente la execvedocumentazione. Non puoi usarlo argv[-1], è un comportamento indefinito usarlo.
Basile Starynkevitch,

Sì, buon punto (anche il suggerimento che abbiamo un syscall) - l'idea è un po 'inventata. Tutti e tre i componenti del runtime (shell, stdlib e OS) dovrebbero collaborare. La shell deve chiamare una speciale funzione non POSIX execvepluscmdcon un parametro aggiuntivo (o convenzione argv), la syscall costruisce un vettore argomento per main che contiene un puntatore alla riga di comando prima del puntatore al nome del programma, quindi passa l'indirizzo del puntatore al nome del programma come argvquando si chiama il programma main...
Peter - Ripristina Monica il

Non è necessario riscrivere la shell, basta usare le virgolette. Questa funzione era disponibile dalla shell bourn sh. Quindi non è nuovo.
ctrl-alt-delor

L'uso delle virgolette richiede di modificare la riga di comando. E OP non lo vuole
Basile Starynkevitch il

3

Puoi sempre dire alla tua shell di dire alle applicazioni quale codice della shell porta alla loro esecuzione. Ad esempio, con zsh, passando quelle informazioni nella $SHELL_CODEvariabile d'ambiente usando l' preexec()hook ( printenvusato come esempio, useresti getenv("SHELL_CODE")nel tuo programma):

$ 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

Tutti quelli sarebbero eseguiti printenvcome:

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

Permettere printenvdi recuperare il codice zsh che porta all'esecuzione di printenvcon quegli argomenti. Quello che vorresti fare con queste informazioni non mi è chiaro.

Con bash, la caratteristica più vicina a quella che zshuserebbe preexec()sarebbe la sua $BASH_COMMANDin una DEBUGtrappola, ma nota che bashfa un certo livello di riscrittura in quello (e in particolare i refactor alcuni degli spazi bianchi usati come delimitatore) e che viene applicato a ogni (bene, alcuni) comando eseguire, non l'intera riga di comando immessa al prompt (vedere anche l' functraceopzione).

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

Guarda come alcuni degli spazi che sono delimitatori nella sintassi del linguaggio shell sono stati compressi in 1 e come non sempre la riga di comando completa non viene sempre passata al comando. Quindi probabilmente non è utile nel tuo caso.

Nota che non consiglierei di fare questo tipo di cose, poiché potenzialmente stai perdendo informazioni sensibili ad ogni comando come in:

echo very_secret | wc -c | untrustedcmd

perderebbe quel segreto a entrambi wce untrustedcmd.

Certo, potresti fare quel genere di cose per altre lingue oltre alla shell. Ad esempio, in C, è possibile utilizzare alcune macro che esportano il codice C che esegue un comando nell'ambiente:

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

Esempio:

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

Guarda come alcuni spazi sono stati condensati dal pre-processore C come nel caso bash. Nella maggior parte, se non in tutte le lingue, la quantità di spazio utilizzata nei delimitatori non fa alcuna differenza, quindi non sorprende che il compilatore / interprete si prenda qualche libertà con loro qui.


Quando stavo testando questo, BASH_COMMANDnon conteneva gli argomenti di separazione degli spazi bianchi originali, quindi questo non era utilizzabile per la richiesta letterale del PO. Questa risposta include qualche dimostrazione in entrambi i casi per quel particolare caso d'uso?
Charles Duffy,

@CharlesDuffy, volevo solo indicare l'equivalente più vicino del preexec () di zsh in bash (poiché è la shell a cui si riferiva l'OP) e sottolineare che non poteva essere usato per quel caso d'uso specifico, ma sono d'accordo che non lo era molto chiaro. Vedi modifica. Questa risposta vuole essere più generica su come passare il codice sorgente (qui in zsh / bash / C) che ha causato l'esecuzione del comando (non qualcosa di utile, ma spero che nel farlo, e soprattutto con gli esempi, dimostro che non è molto utile)
Stéphane Chazelas

0

Aggiungerò solo ciò che manca nelle altre risposte.

No

Vedi altre risposte

Forse, in un certo senso

Non c'è nulla che possa essere fatto nel programma, ma c'è qualcosa che può essere fatto nella shell quando si esegue il programma.

Devi usare le virgolette. Quindi invece di

./myprog      aaa      bbb

devi fare uno di questi

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

Questo passerà un singolo argomento al programma, con tutti gli spazi. C'è una differenza tra i due, il secondo è letterale, esattamente la stringa come appare (tranne che 'deve essere digitata come \'). Il primo interpreterà alcuni personaggi, ma suddiviso in diversi argomenti. Vedi quotazione della shell per maggiori informazioni. Quindi non è necessario riscrivere la shell, i progettisti della shell ci hanno già pensato. Tuttavia, poiché ora è un argomento, dovrai fare di più passando all'interno del programma.

opzione 2

Passa i dati in via stdin. Questo è il modo normale di ottenere grandi quantità di dati in un comando. per esempio

./myprog << EOF
    aaa      bbb
EOF

o

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

(Il corsivo è l'output del programma)


Tecnicamente, il codice shell: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"esegue (generalmente in un processo figlio) il file archiviato ./myproge gli passa due argomenti: ./myproge ␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]e argc[1], argcessendo 2) e come nei PO, lo spazio che separa questi due argomenti non viene passato in alcun modo a myprog.
Stéphane Chazelas,

Ma stai cambiando il comando e OP non vuole cambiarlo
Basile Starynkevitch

@BasileStarynkevitch Dopo il tuo commento, ho letto di nuovo la domanda. Stai assumendo un presupposto. Da nessuna parte l'OP afferma che non vogliono cambiare il modo in cui viene eseguito il programma. Forse è vero, ma non avevano nulla da dire. Pertanto questa risposta può essere ciò di cui hanno bisogno.
ctrl-alt-delor

OP chiede esplicitamente di spazi tra argomenti, non di un singolo argomento contenente spazi
Basile Starynkevitch
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.