Почему моя программа не принимает должным образом выходные данные другой программы? - PullRequest
0 голосов
/ 11 ноября 2018

У меня есть программа на C, скомпилированная с 3 файлами .c. По сути, эта программа выводит квадраты на стандартный вывод на основе ввода размера x и y, который я определил в основном. Соответствующий код ниже:

void    rush(int x, int y);

int     main(void)
{
    rush(3, 3);
    return (0);
}

запуск исполняемого файла main следующим образом:

./a.out

дает следующее:

o-o
| |
o-o

и изменение параметров, передаваемых в функцию rush на (5, 5), приводит к следующему:

o---o
|   |
|   |
|   |
o---o

Вы поняли идею. Каждая строка ограничена символом \ n, который позволяет функции печатать правильную следующую строку. У меня есть другая тестовая программа, которая представляет собой простую скомпилированную основную часть, которая просто печатает значение ARGC, так как я хотел проверить поведение того, что будет давать такой ввод. Вторая основная программа выглядит так:

#include <stdio.h>

int     main(int argc, char **argv)
{
    printf("argc value is: %d\n", argc);
    return (0);
}

Выполнение следующих команд:

./a.out | ./test

Я получаю следующий вывод:

argc value is: 1

Что изначально не имело для меня смысла, но потом я вспомнил, что это потому, что некоторые команды требуют, чтобы xargs правильно принимал ввод от stdin. Использование xargs с (5, 5) в качестве входных данных в основном:

./a.out | xargs ./test

В результате:

argc value is: 9

Таким образом, у меня есть два вопроса. Есть ли способ сделать это без необходимости xargs и может быть сделано в самих файлах c? И зная ввод в тестовый файл, почему argc == 9? Как программа выделяет строку в этом формате и решает, что поместить в массив?

Ответы [ 2 ]

0 голосов
/ 11 ноября 2018

Это будет долго, поэтому возьмите свой любимый напиток. Не просто переходите к ответам после перерыва.

Сначала рассмотрим аргументы командной строки, предоставляемые программе, скажем args.c :

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    int  i;
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
        printf("argv[%d] = \"%s\"\n", i, argv[i]);
    return EXIT_SUCCESS;
}

Скомпилируйте это, используя ваш любимый компилятор C; Я использую GCC:

gcc -Wall -O2 args.c -o args

Если вы бежите, скажите

./args one two

будет выводиться

argc = 3
argv[0] = "./args"
argv[1] = "one"
argv[2] = "two"

Все Unix имеют встроенную утилиту командной строки или оболочку printf, которая работает так же, как стандартная библиотечная функция C printf(). Мы можем запустить, например,

printf 'Hello, world!\nSecond line\nThird line\n'

и мы увидим

Hello, world!
Second line
Third line

Теперь, если мы соединим их с помощью трубы,

printf 'Hello, world!\nSecond line\nThird line\n' | ./args

получаем

argc = 1
argv[0] = "./args"

потому что не было параметров для ./args, а приведенный выше args.c полностью игнорирует стандартный ввод.

Служебная команда xargs читает входные данные в нее, а затем выполняет свои собственные аргументы командной строки в качестве команды, добавляя входные данные, которые она читает, в качестве дополнительных параметров. Это также легко настраивается. Если вы запускаете

printf 'Hello, world!\nSecond line\nThird line\n' | xargs ./args

вы получите

argc = 7
argv[0] = "./args"
argv[1] = "Hello,"
argv[2] = "world!"
argv[3] = "Second"
argv[4] = "line"
argv[5] = "Third"
argv[6] = "line"

потому что xargs превращает каждый токен во входных данных, разделенных пробелами, в аргумент командной строки. Если мы скажем xargs превратить каждую строку ввода в отдельный аргумент, используя опцию -d SEPARATOR, с символом новой строки в качестве разделителя:

printf 'Hello, world!\nSecond line\nThird line\n' | xargs -d '\n' ./args

получаем

argc = 4
argv[0] = "./args"
argv[1] = "Hello, world!"
argv[2] = "Second line"
argv[3] = "Third line"

Если мы скажем xargs добавить не более двух аргументов для каждой выполняемой команды, добавив параметр -n 2,

printf 'Hello, world!\nSecond line\nThird line\n' | xargs -d '\n' -n 2 ./args

мы получим

argc = 3
argv[0] = "./args"
argv[1] = "Hello, world!"
argv[2] = "Second line"
argc = 2
argv[0] = "./args"
argv[1] = "Third line"

Этот вывод означает, что наш ./args был фактически выполнен дважды. Первый был эффективно ./args 'Hello, world!' 'Second line', а второй был ./args 'Third line'.

Другая важная опция для xargs - -r, которая запрещает запускать команду без дополнительных аргументов:

true | xargs -r ./args

ничего не выводит, потому что xargs не видит входных данных, а опция -r говорит ему не запускать нашу программу args, если нет дополнительных аргументов.

При манипулировании именами файлов или путями опция -0 (тире ноль) сообщает xargs, что разделитель ввода - это нулевой символ, \0, который в C ограничивает строки. Если мы используем это во входных данных для xargs, даже строки с символами новой строки и тому подобное будут правильно разбиты на аргументы. Например:

printf 'One thing\non two lines\0Second thing' | xargs -0 ./args

выдаст

argc = 3
argv[0] = "./args"
argv[1] = "One thing
on two lines"
argv[2] = "Second thing"

Это именно то, что нужно, если обрабатывать имена файлов или пути надежным способом.


Есть ли способ сделать это без необходимости использования xargs и сделать это можно в самих файлах c?

Конечно: просто прочитайте стандартный ввод. xargs почти наверняка написан на самом C на всех системах Unixy.

Как [xargs] выделяет строку в этом формате и решает, что поместить в массив?

Короткий ответ: все зависит от используемых опций, потому что xargs - довольно мощный маленький инструмент.

Полный ответ: посмотрите на источники. Источник для GNU xargs (часть findutils) здесь , а источник для версии FreeBSD здесь .

Ответ кода зависит от того, можете ли вы использовать POSIX.1 или нет, в частности getline() или getdelim(). Если у вас есть односимвольный разделитель (будь то один однобайтовый символ вообще, даже nul), вы можете использовать getdelim(), чтобы получить каждый «параметр» из ввода как отдельную строку. Это то, что я бы сделал, но это не , это решение . (В настоящее время, если у вас есть обслуживаемый компьютер Unixy, он почти наверняка будет иметь поддержку POSIX.1 во встроенной библиотеке C).

Почему argc == 9?

Если мы продублируем ваш ввод с помощью printf 'o---o\n| |\n| |\n| |\no---o\n' и перенаправим его на xargs ./args, вывод будет таким, как ожидалось,

argc = 9
argv[0] = "./args"
argv[1] = "o---o"
argv[2] = "|"
argv[3] = "|"
argv[4] = "|"
argv[5] = "|"
argv[6] = "|"
argv[7] = "|"
argv[8] = "o---o"

т.е. каждая часть вашего ascii-кода отделяется пробелами и передается в качестве параметра командной строки. Если мы передадим его по каналу xargs -d '\n' ./args, результат будет

argc = 6
argv[0] = "./args"
argv[1] = "o---o"
argv[2] = "|   |"
argv[3] = "|   |"
argv[4] = "|   |"
argv[5] = "o---o"

Если бы вы написали эту первоначальную программу args.c для себя, вы, вероятно, могли бы найти ответ на свои вопросы самостоятельно через исследование. Вот что делает программирование таким мощным: вы можете написать инструменты, которые помогут вам понять проблемы, которые вы хотите решить. Применение философии Unix и принципа KISS означает, что эти инструменты часто довольно просты в написании. Просто напишите их в первую очередь, чтобы вы могли доверять их результатам, и вам не нужно переписывать их слишком часто.

0 голосов
/ 11 ноября 2018

Это происходит потому, что xargs берет весь ввод (все строки, а не только одну строку) и разбивает его на символы пробела. Итак, аргументы, которые получает ваш тестовый код (вы можете напечатать их самостоятельно для отладки):

  1. . / Тест
  2. о --- о
  3. |
  4. |
  5. |
  6. |
  7. |
  8. |
  9. о --- о

Если вы хотели читать из стандартного ввода, а не анализировать аргументы, используйте cin >> string_variable.

...