Это будет долго, поэтому возьмите свой любимый напиток. Не просто переходите к ответам после перерыва.
Сначала рассмотрим аргументы командной строки, предоставляемые программе, скажем 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()
, чтобы получить каждый «параметр» из ввода как отдельную строку. Это то, что я бы сделал, но это не unix , это решение posix . (В настоящее время, если у вас есть обслуживаемый компьютер 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 означает, что эти инструменты часто довольно просты в написании. Просто напишите их в первую очередь, чтобы вы могли доверять их результатам, и вам не нужно переписывать их слишком часто.