Чи можливо програмі отримати кількість пробілів між аргументами командного рядка в 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.


22
Для цікавості, чому ваша програма повинна знати це?
nxnev

2
@nxnev Я писав деякі програми Windows, і я знаю, що це можливо там, тому мені цікаво, чи є щось подібне в Linux (або Unix).
iBug

9
Я неясно пам'ятаю в CP / M, що програми мали розбирати власні командні рядки - це означало, що кожен C час виконання повинен був реалізувати аналізатор оболонки. І всі вони це зробили трохи інакше.
Toby Speight

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
@ Ганс-МартинМоснер: Термукс ...? ;-)
DevSolar

9
"взагалі" малося на увазі як захист від цитування спеціального згорнутого випадку, коли це можливо - наприклад, процес suid root може бути в змозі перевірити пам'ять оболонки виклику і знайти нерозділений рядок командного рядка.
Ганс-Мартін Моснер

16

Ні, це неможливо, якщо пробіли не є частиною аргументу.

Команда отримує доступ до окремих аргументів з масиву (в тій чи іншій формі, залежно від мови програмування), а власне командний рядок може бути збережено у файл історії (якщо він набраний в інтерактивному рядку в оболонці, що має файли історії), але це ніколи не передавали команду в будь-якій формі.

Усі команди на Unix врешті-решт виконуються однією з exec()сімейств функцій. Вони беруть ім'я команди та список або масив аргументів. Жоден з них не приймає командний рядок, введений у командному рядку. system()Функція робить, але його аргументи рядки пізніше виконуються execve(), що, знову - таки, приймає масив аргументів , а не рядок командного рядка.


2
@LightnessRacesinOrbit Я зазначив, що там на всякий випадок є якась плутанина щодо "пробілів між аргументами". Поставлення пробілів у лапки між helloі worldє буквально пробілами між двома аргументами.
Kusalananda

5
@Kusalananda - Ну, немає ... Введення прогалин в лапках між helloі worldв буквальному сенсі подача другого з трьох аргументів.
Джеремі

@Jeremy Як я вже говорив, у разі виникнення плутанини щодо того, що малося на увазі під "між аргументами". Так, як другий аргумент між двома іншими, якщо ви хочете.
Кусалаланда

Ваші приклади були чудовими та повчальними.
Джеремі

1
Ну, хлопці, приклади були очевидним джерелом плутанини та нерозуміння. Я видалив їх, оскільки значення не відповіло значенню.
Кусалаланда

9

Загалом, це неможливо, як пояснено декілька інших відповідей.

Однак оболонки Unix є звичайними програмами (і вони інтерпретують командний рядок і глобалізують його, тобто розширюють команду перед тим, як робити fork& execvefor). Дивіться це пояснення щодо bashоперацій з оболонкою . Ви можете написати власну оболонку (або ви зможете виправити якусь наявну оболонку вільного програмного забезпечення , наприклад, GNU bash ) і використовувати її як свою оболонку (або навіть свою оболонку для входу, див. Passwd (5) & shell (5) ).

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

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

BTW, програма може бути запущена якоюсь програмою, яка не є оболонкою (але яка виконує вилку (2), потім виконує (2) , або просто execveзапускає програму в її поточному процесі). У такому випадку командного рядка взагалі немає, і вашу програму можна було запустити без команди ...

Зауважте, що у вас може бути встановлена ​​деяка (спеціалізована) система Linux без встановленої оболонки. Це дивно і незвично, але можливо. Тоді вам потрібно буде написати спеціалізовану програму init, запускаючи інші програми за потребою - не використовуючи оболонки, але виконуючи fork& execveсистемні дзвінки.

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


Це найкраща відповідь. Я припускаю (не дивлячись на це вгору), що argv[0] для назви програми та решти елементів для аргументів є специфікаціями POSIX і їх неможливо змінити. Навколишнє середовище виконання може вказати argv[-1]для командного рядка, я припускаю ...
Пітер - Відновити Моніку

Ні, не міг. Детальніше читайте execveдокументацію. Ви не можете використовувати argv[-1], це невизначена поведінка, щоб використовувати його.
Василь Старинкевич

Так, хороший момент (також натяк на те, що у нас є систематичний виклик) - ідея трохи надумана. Усі три компоненти часу виконання (оболонка, stdlib та ОС) потребують співпраці. Оболонці потрібно викликати спеціальну execvepluscmdфункцію не POSIX з додатковим параметром (або умовою argv), syscall будує вектор аргументу для основного, який містить вказівник на командний рядок перед вказівником на ім'я програми, а потім передає адресу вказівника на назву програми, як argvпід час виклику програми main...
Пітер - Поновіть Моніку

Не потрібно переписувати оболонку, просто використовуйте цитати. Ця функція була доступна в оболонці 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не містилося оригінального аргументу, що розділяє пробіл, тому це було непридатним для прямого запиту ОП. Чи містить ця відповідь будь-яка демонстрація в будь-якому випадку для конкретного випадку використання?
Чарльз Даффі

@CharlesDuffy, я просто хотів вказати найближчий еквівалент zsh's preexec () у bash (так як це оболонка, на яку переглядав ОП) і зазначив, що він не може бути використаний для конкретного випадку використання, але я згоден, що це не було дуже чітко. Див. Редагування. Ця відповідь має бути більш загальною щодо того, як передати вихідний код (тут в zsh / bash / C), що спричинило виконання виконуваної команди (не те, що корисно, але я сподіваюся, що при цьому, і особливо з прикладами я демонструю, що це не дуже корисно)
Стефан Шазелас

0

Я просто додам те, чого не вистачає в інших відповідях.

Ні

Дивіться інші відповіді

Можливо, начебто

У програмі нічого не можна зробити, але є щось, що можна зробити в оболонці, коли ви запускаєте програму.

Потрібно використовувати лапки. Тож замість

./myprog      aaa      bbb

вам потрібно зробити одне з них

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

Це передасть єдиний аргумент програмі з усіма пробілами. Існує різниця між двома, друге є буквальним, саме такий рядок, як він є (за винятком того, що він 'повинен бути введений як \'). Перший буде інтерпретувати деякі символи, але розділиться на кілька аргументів. Дивіться цитування оболонок для отримання додаткової інформації. Тому не потрібно переписувати оболонку, дизайнери оболонок вже про це подумали. Однак, оскільки це вже один аргумент, вам доведеться зробити більше проходження в рамках програми.

Варіант 2

Передайте дані через stdin. Це звичайний спосіб отримання великої кількості даних в команду. напр

./myprog << EOF
    aaa      bbb
EOF

або

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

(Курсиви виводяться з програми)


Технічно код оболонки: ./myprog␣"␣␣␣␣␣aaa␣␣␣␣␣␣bbb"виконує (як правило, у дочірньому процесі) файл, що зберігається в ньому, ./myprogі передає йому два аргументи: ./myprogі ␣␣␣␣␣aaa␣␣␣␣␣␣bbb( argv[0]і argc[1], argcбудучи 2), і як в ОП, простір, що розділяє ці два аргументи, не пропускається жодним чином до myprog.
Стефан Шазелас

Але ви змінюєте команду, і ОП не хочуть її змінювати
Базиль Старинкевич

@BasileStarynkevitch Після вашого коментаря я прочитав питання ще раз. Ви робите припущення. Ніде ОП не говорить, що вони не хочуть змінювати спосіб виконання програми. Можливо, це правда, але вони не мали про що сказати. Тому ця відповідь може бути тим, що їм потрібно.
ctrl-alt-delor

ОП запитують прямо про пробіли між аргументами, а не про один єдиний аргумент, що містить пробіли
Базиль Старинкевич,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.