É possível que um programa obtenha um número de espaços entre os argumentos da linha de comando no POSIX?

22

Diga se eu escrevi um programa com a seguinte linha:

int main(int argc, char** argv)

Agora, ele sabe quais argumentos da linha de comando são passados para ele, verificando o conteúdo de argv .

O programa pode detectar quantos espaços entre argumentos? Tipo quando eu digito no bash:

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

O ambiente é um Linux moderno (como o Ubuntu 16.04), mas suponho que a resposta deva se aplicar a qualquer sistema compatível com POSIX.

    
por iBug 15.03.2018 / 07:10

6 respostas

38

Não é significativo falar em "espaços entre argumentos"; isso é um conceito de shell.

O trabalho de um shell é pegar linhas inteiras de entrada e formá-las em arrays de argumentos para iniciar comandos com. Isso pode envolver a análise de strings entre aspas, variáveis de expansão, curingas de arquivos e expressões de til, e mais. O comando é iniciado com uma chamada de sistema padrão exec , que aceita um vetor de strings.

Existem outras maneiras de criar um vetor de strings. Muitos programas separam e executam seus próprios subprocessos com invocações de comandos predeterminadas - neste caso, nunca existe uma "linha de comando". Da mesma forma, um shell gráfico (desktop) pode iniciar um processo quando um usuário arrasta um ícone de arquivo e o coloca em um widget de comando - novamente, não há linha textual para ter caracteres "entre" argumentos.

No que diz respeito ao comando invocado, o que acontece em um shell ou outro processo pai / precursor é privado e oculto - vemos apenas o array de strings que o padrão C especifica que main() pode aceitar.

    
por 15.03.2018 / 10:39
58

Em geral, não. A análise de linha de comando é feita pelo shell, que não disponibiliza a linha não analisada para o programa chamado. De fato, seu programa pode ser executado a partir de outro programa que criou o argv não analisando uma string, mas construindo uma matriz de argumentos programaticamente.

    
por 15.03.2018 / 07:16
16

Não, isso não é possível, a menos que os espaços sejam parte de um argumento.

O comando acessa os argumentos individuais de uma matriz (de uma forma ou de outra dependendo da linguagem de programação) e a linha de comando real pode ser salva em um arquivo de histórico (se for digitado em um prompt interativo em um shell que tenha arquivos de histórico) , mas nunca é passado para o comando de qualquer forma.

Todos os comandos no Unix são executados no final por uma das famílias de funções exec() . Estes levam o nome do comando e uma lista ou matriz de argumentos. Nenhum deles usa uma linha de comando conforme digitado no prompt do shell. A função system() , mas seu argumento de string é executado posteriormente por execve() , que, novamente, recebe uma matriz de argumentos em vez de uma cadeia de linha de comando.

    
por 15.03.2018 / 07:56
9

Em geral, não é possível, como várias outras respostas explicadas.

No entanto, shells Unix são programas comuns (e estão interpretando a linha de comando e globbing , ou seja, expandindo o comando antes de fazer fork & execve para isso). Veja esta explicação sobre bash operações shell . Você poderia escrever seu próprio shell (ou você poderia corrigir alguns software livre shell, por exemplo, GNU bash ) e usá-lo como seu shell (ou até mesmo o seu shell de login, veja passwd (5) & shells(5) ).

Por exemplo, você pode ter o próprio seu programa shell colocando a linha de comando completa em alguma variável de ambiente (imagine MY_COMMAND_LINE por exemplo) ou usar qualquer outro tipo de comunicação entre processos para transmitir a linha de comando do shell para o processo filho -.

Eu não entendo por que você gostaria de fazer isso, mas você pode codificar um shell se comportando de tal maneira (mas eu recomendo que não faça isso).

BTW, um programa pode ser iniciado por algum programa que não é uma shell (mas que fork (2) então execve (2) , ou apenas um execve para iniciar um programa em seu processo atual). Nesse caso, não há linha de comando, e seu programa pode ser iniciado sem um comando ...

Observe que você pode ter algum sistema Linux (especializado) sem qualquer shell instalado. Isso é estranho e incomum, mas é possível. Você então precisará escrever um programa especializado init iniciando outros programas conforme necessário - sem usar qualquer shell, mas fazendo fork & execve de chamadas do sistema.

Leia também Sistemas operacionais: três partes fáceis e não esqueça que execve é praticamente sempre uma chamada de sistema (no Linux, eles estão listados em syscalls (2) , veja também intro(2) ) que reinicializa o espaço de endereçamento virtual ( e algumas outras coisas) do processo fazendo isso.

    
por 15.03.2018 / 21:06
3

Você sempre pode dizer ao seu shell para informar aos aplicativos qual código shell leva à sua execução. Por exemplo, com zsh , passando essas informações na variável de ambiente $SHELL_CODE usando o preexec() hook ( printenv usado como exemplo, você usaria getenv("SHELL_CODE") em seu programa):

$ 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

Todos os que executam printenv como:

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

Permitindo que printenv recupere o código zsh que leva à execução de printenv com esses argumentos. O que você gostaria de fazer com essa informação não está claro para mim.

Com bash , o recurso mais próximo de zsh ' preexec() estaria usando sua $BASH_COMMAND em DEBUG trap, mas observe que bash faz algum nível de reescrita nisso (e em Refatorar alguns dos espaços em branco usados como delimitador) e isso é aplicado a toda execução de comando (bem, algumas), não à linha de comando inteira como inserida no prompt (consulte também a opção functrace ).

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

Veja como alguns dos espaços delimitadores na sintaxe da linguagem shell foram espremidos em 1 e como a linha de comando completa nem sempre é passada para o comando. Então provavelmente não é útil no seu caso.

Note que eu não aconselharia fazer esse tipo de coisa, já que você está potencialmente vazando informações confidenciais para todos os comandos, como em:

echo very_secret | wc -c | untrustedcmd

vazaria esse segredo para wc e untrustedcmd .

Claro, você poderia fazer esse tipo de coisa para outros idiomas além do shell. Por exemplo, em C, você pode usar algumas macros que exportam o código C que executa um comando para o 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;
}

Exemplo:

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

Veja como alguns espaços foram condensados pelo pré-processador C, como no caso do bash. Na maioria, se não em todas as linguagens, a quantidade de espaço usada nos delimitadores não faz diferença, portanto, não é surpresa que o compilador / intérprete tenha alguma liberdade com eles aqui.

    
por 17.03.2018 / 10:20
0

Vou apenas adicionar o que está faltando nas outras respostas.

Não

Veja outras respostas

Talvez, mais ou menos

Não há nada que possa ser feito no programa, mas há algo que pode ser feito no shell quando você executa o programa.

Você precisa usar aspas. Então, ao invés de

./myprog      aaa      bbb

você precisa fazer um desses

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

Isso passará um único argumento para o programa, com todos os espaços. Há uma diferença entre os dois, o segundo é literal, exatamente a string como aparece (exceto que ' deve ser digitado como \' ). O primeiro interpretará alguns caracteres, mas será dividido em vários argumentos. Veja o shell citando para mais informações. Portanto, não é necessário reescrever o shell, os projetistas de shell já pensaram nisso. No entanto, porque agora é um argumento, você terá que passar mais dentro do programa.

Opção 2

Transmita os dados via stdin. Essa é a maneira normal de obter grandes quantidades de dados em um comando. por exemplo,

./myprog << EOF
    aaa      bbb
EOF

ou

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

(Itálico é a saída do programa)

    
por 18.03.2018 / 10:17