Может ли программа получить количество пробелов между аргументами командной строки в POSIX?

23

Скажите, если я написал программу со следующей строкой:

int main(int argc, char** argv)

Теперь он знает, какие аргументы командной строки передаются ему, проверяя содержимое argv.

Может ли программа определить, сколько пробелов между аргументами? Например, когда я набираю их в bash:

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

Среда - это современный Linux (например, Ubuntu 16.04), но я полагаю, что ответ должен относиться к любым POSIX-совместимым системам.

iBug
источник
22
Просто для любопытства, зачем вашей программе это знать?
nxnev
2
@nxnev Я писал некоторые программы для Windows и знаю, что это возможно, поэтому мне интересно, есть ли что-то подобное в Linux (или Unix).
iBug
9
Я смутно помню в CP / M, что программы должны были анализировать свои собственные командные строки - это означало, что каждая среда выполнения C должна была реализовать синтаксический анализатор оболочки. И все они сделали это немного по-другому.
Тоби Спейт
3
@iBug Есть, но вам нужно заключать аргументы в кавычки при вызове команды. Вот как это делается на POSIX (и подобных) оболочках.
Конрад Рудольф
3
@iBug, ... Windows имеет тот же дизайн, что Тоби упоминает из CP / M выше. UNIX не делает этого - с точки зрения вызываемого процесса, там нет никакой командной строки , участвующих в ее запуска.
Чарльз Даффи

Ответы:

39

Не имеет смысла говорить о «пробелах между аргументами»; это концепция оболочки.

Работа оболочки состоит в том, чтобы взять целые строки ввода и сформировать их в массивы аргументов для запуска команд. Это может включать анализ строк в кавычках, расширение переменных, подстановочные знаки файлов и выражения тильды и многое другое. Команда запускается стандартным execсистемным вызовом, который принимает вектор строк.

Существуют и другие способы создания вектора строк. Многие программы разрабатывают и выполняют свои собственные подпроцессы с предопределенными вызовами команд - в этом случае никогда не бывает такой вещи, как «командная строка». Точно так же графическая оболочка (на рабочем столе) может запустить процесс, когда пользователь перетаскивает значок файла и помещает его в командный виджет - опять же, нет текстовой строки, в которой бы были символы между аргументами.

Что касается вызываемой команды, то, что происходит в оболочке или другом родительском / предшественнике, является частным и скрытым - мы видим только тот массив строк, который стандарт C определяет, который main()может принять.

Тоби Спейт
источник
Хороший ответ - важно указать на это новичкам в Unix, которые часто предполагают, что если они запустятся, tar cf texts.tar *.txtто программа tar получит два аргумента и *.txtсама должна развернуть второй ( ). Многие люди не понимают, как это действительно работает, пока не начнут писать свои собственные скрипты / программы, которые обрабатывают аргументы.
Лоуренс Реншоу
58

В общем нет. Разбор командной строки выполняется оболочкой, которая не делает непарсированную строку доступной для вызываемой программы. Фактически, ваша программа может выполняться из другой программы, которая создала argv не путем разбора строки, а путем программного создания массива аргументов.

Ханс-Мартин Моснер
источник
9
Вы можете упомянуть execve(2).
iBug
3
Вы правы, в качестве отмазки я могу сказать, что в настоящее время пользуюсь телефоном, а поиск
справочных
1
Это соответствующий раздел POSIX.
Стивен Китт
1
@ Ханс-МартинМоснер: Termux ...? ;-)
DevSolar
9
«в общем» означало защиту от цитирования особого запутанного случая, где это возможно - например, процесс suid root мог бы проверить память вызывающей оболочки и найти строку без разбора командной строки.
Ханс-Мартин Моснер
16

Нет, это невозможно, если только пробелы не являются частью аргумента.

Команда обращается к отдельным аргументам из массива (в той или иной форме в зависимости от языка программирования), и фактическая командная строка может быть сохранена в файле истории (если она введена в интерактивном режиме в оболочке, имеющей файлы истории), но никогда не передается команде в любой форме.

Все команды в Unix в конце выполняются одной exec()из функций семейства. Они принимают имя команды и список или массив аргументов. Ни один из них не использует командную строку, введенную в командной строке. system()Функция делает, но его аргументы строки позже выполняются execve(), что, опять - таки, принимает массив аргументов , а не строка командной строки.

Кусалананда
источник
2
@LightnessRacesinOrbit Я поместил это на всякий случай, если возникла путаница с «пробелами между аргументами». Помещение пробелов в кавычки между helloи worldявляется буквально пробелами между двумя аргументами.
Кусалананда
5
@Kusalananda - Ну, нет ... Ввод пробелов в кавычках между helloи worldв буквальном смысле подача второго из трех аргументов.
Джереми
@ Джереми Как я уже сказал, на случай, если возникнет путаница в том, что подразумевается под «между аргументами». Да, как второй аргумент между двумя другими, если хотите.
Кусалананда
Ваши примеры были хорошими и поучительными.
Джереми
1
Ну, ребята, примеры были очевидным источником путаницы и недопонимания. Я удалил их, поскольку не добавил к значению ответа.
Кусалананда
9

В общем, это невозможно, как объяснили несколько других ответов.

Тем не менее, оболочки Unix являются обычными программами (и они интерпретируют командную строку и ее глобализацию , то есть расширяют команду перед выполнением fork& execveдля нее). Смотрите это объяснение об bashоперациях с оболочкой . Вы можете написать свою собственную оболочку (или вы можете исправить какую-то существующую оболочку свободного программного обеспечения , например GNU bash ) и использовать ее в качестве оболочки (или даже оболочки для входа в систему, см. Passwd (5) & shells (5) ).

Например, ваша собственная программа оболочки может поместить полную командную строку в некоторую переменную среды (представьте, MY_COMMAND_LINEнапример) - или использовать любой другой тип межпроцессного взаимодействия для передачи командной строки из оболочки в дочерний процесс-.

Я не понимаю, почему вы хотели бы сделать это, но вы могли бы кодировать оболочку, которая ведет себя таким образом (но я рекомендую не делать этого).

Кстати, программа может быть запущена некоторой программой, которая не является оболочкой (но которая выполняет fork (2), а затем execve (2) , или просто execveдля запуска программы в текущем процессе). В этом случае вообще нет командной строки, и ваша программа может быть запущена без команды ...

Обратите внимание, что у вас может быть какая-то (специализированная) система Linux без какой-либо установленной оболочки. Это странно и необычно, но возможно. Затем вам нужно будет написать специализированную программу инициализации, запускающую другие программы по мере необходимости - без использования какой-либо оболочки, но с помощью fork& execveсистемных вызовов.

Читайте также Операционные системы: три простых части, и не забывайте, что execveэто практически всегда системный вызов (в Linux они перечислены в syscalls (2) , см. Также intro (2) ), которые повторно инициализируют виртуальное адресное пространство (и некоторые другие вещи) процесса, делающего это.

Василий Старынкевич
источник
Это лучший ответ. Я предполагаю (не смотря на это), что argv[0] имя программы и остальные элементы аргументов являются спецификациями POSIX и не могут быть изменены. Среда выполнения может указывать argv[-1]для командной строки, я полагаю ...
Питер - Восстановить Монику
Нет, не мог. Читайте более внимательно execveдокументацию. Вы не можете использовать argv[-1], это неопределенное поведение, чтобы использовать его.
Василий Старынкевич,
Да, хорошая мысль (также намек на то, что у нас есть системный вызов) - идея немного надумана. Все три компонента среды выполнения (shell, stdlib и OS) должны будут сотрудничать. Оболочке необходимо вызвать специальную не-POSIX- execvepluscmdфункцию с дополнительным параметром (или соглашением argv), системный вызов создает вектор аргумента для main, который содержит указатель на командную строку перед указателем на имя программы, а затем передает адрес указателя на имя программы, как argvпри вызове программы main...
Питер - Восстановить Монику
Не нужно переписывать оболочку, просто используйте кавычки. Эта функция была доступна из оболочки Bourn sh. Так что не нова.
Ctrl-Alt-Delor
Использование кавычек требует изменения командной строки. И ОП этого не хочет
Василий Старынкевич
3

Вы всегда можете указать своей оболочке сообщить приложениям, какой код оболочки приводит к их выполнению. Например, с zshпомощью передачи этой информации в $SHELL_CODEпеременную окружения с помощью preexec()ловушки ( printenvиспользуется в качестве примера, вы будете использовать getenv("SHELL_CODE")в своей программе):

$ 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

Все они будут выполнены printenvкак:

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

Позволяет printenvполучить код zsh, который приводит к выполнению printenvс этими аргументами. Что бы вы хотели сделать с этой информацией, мне неясно.

С bashпомощью функции, ближайшей к zsh's preexec(), будет использовать ее $BASH_COMMANDв DEBUGловушке, но обратите внимание, что в этом bashесть некоторый уровень переписывания (и, в частности, рефакторинг некоторых пробельных символов, используемых в качестве разделителя), и это применяется к каждой (ну, некоторым) команде запустить, а не всю командную строку, как указано в приглашении (см. также 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')

Посмотрите, как некоторые из пробелов, которые являются разделителями в синтаксисе языка оболочки, были сжаты в 1 и как не вся командная строка не всегда передается команде. Так что, вероятно, не полезно в вашем случае.

Обратите внимание, что я бы не советовал делать такие вещи, так как вы потенциально можете передавать конфиденциальную информацию каждой команде, как в:

echo very_secret | wc -c | untrustedcmd

просочится эту тайну как wcи untrustedcmd.

Конечно, вы могли бы делать такие вещи для других языков, кроме оболочки. Например, в C вы можете использовать некоторые макросы, которые экспортируют код C, который выполняет команду в среду:

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

Пример:

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

Посмотрите, как некоторые пробелы были сжаты препроцессором C, как в случае bash. В большинстве, если не во всех языках, объем пространства, используемого в разделителях, не имеет значения, поэтому не удивительно, что компилятор / интерпретатор берет на себя некоторую свободу здесь.

Стефан Шазелас
источник
Когда я тестировал это, BASH_COMMANDон не содержал исходных пробелов, разделяющих аргументы, поэтому его нельзя было использовать для буквального запроса OP. Включает ли этот ответ какую-либо демонстрацию в любом случае для этого конкретного варианта использования?
Чарльз Даффи
@CharlesDuffy, я просто хотел указать ближайший эквивалент preshec () zsh в bash (так как это оболочка, на которую ссылался OP) и указать, что его нельзя использовать для этого конкретного случая использования, но я согласен, что это не так очень ясно. Смотрите редактировать. Этот ответ должен быть более общим о том, как передать исходный код (здесь, в zsh / bash / C), который вызвал выполнение исполняемой команды (не то, что полезно, но я надеюсь, что при этом, и особенно на примерах я демонстрирую, что это не очень полезно)
Стефан Шазелас
0

Я просто добавлю то, чего не хватает в других ответах.

нет

Смотрите другие ответы

Может быть, вроде

В программе ничего нельзя сделать, но есть что-то, что можно сделать в оболочке при запуске программы.

Вам нужно использовать кавычки. Так что вместо

./myprog      aaa      bbb

вам нужно сделать один из этих

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

Это передаст в программу один аргумент со всеми пробелами. Между ними есть различие, второе буквальное, в точности такая строка, как кажется (за исключением того, что 'должно быть напечатано как \'). Первый будет интерпретировать некоторые символы, но будет разбит на несколько аргументов. См. Цитирование оболочки для получения дополнительной информации. Поэтому не нужно переписывать оболочку, дизайнеры оболочки уже подумали об этом. Однако, поскольку теперь это один аргумент, вам придется больше проходить внутри программы.

Вариант 2

Передайте данные через стандартный ввод. Это нормальный способ получить большие объемы данных в команду. например

./myprog << EOF
    aaa      bbb
EOF

или

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

(Курсив - вывод программы)

Ctrl-Alt-Делор
источник
Технически, код оболочки: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"выполняет (обычно в дочернем процессе) файл, хранящийся в нем, ./myprogи передает ему два аргумента: ./myprogи ␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]и argc[1], argcбудучи равным 2), и, как и в OP, пространство, разделяющее эти два аргумента, никоим образом не передается к myprog.
Стефан Шазелас
Но вы меняете команду, а ОП не хочет ее менять
Василий Старынкевич
@BasileStarynkevitch После вашего комментария я перечитал вопрос еще раз. Вы делаете предположение. ОП нигде не говорит, что они не хотят менять способ запуска программы. Возможно, это правда, но им нечего было сказать по этому поводу. Поэтому этот ответ может быть тем, что им нужно.
Ctrl-Alt-Delor
ОП спрашивает явно о пробелах между аргументами, а не об одном-единственном аргументе, содержащем пробелы
Василий Старынкевич,