Окружающая среда
ОС: Ubunty 20.4, Centos 8, macOS Catalina 10.15.7
Язык: C, C++
Компилятор: gcc (самые последние версии для каждой ОС)
Проблема
Я использую библиотечную функцию wordexp Posix, чтобы получить расширение строк, подобное оболочке.
Расширение работает нормально, за одним исключением: когда я устанавливаю переменную среды $IFS на что-то отличное от пробела, например ':', похоже, это не влияет на разделение слов, которое по-прежнему выполняется только по пробелам, независимо от значения IFS.
bash тест
Справочная страница wordexp для Linux https://man7.org/linux/man-pages/man3/wordexp.3.html гласит:
- Функция wordexp() выполняет расширение строки в виде оболочки...
- Разделение полей выполняется с помощью переменной среды $IFS. Если он не установлен, разделителями полей являются пробел, табуляция и новая строка.
Вот почему я ожидал, что wordexp будет вести себя так же, как bash в этом отношении.
Во всех перечисленных ОС я получил точно такой же правильный и ожидаемый результат, когда изменение набора символов, используемого для разделения:
Использование по умолчанию (IFS не установлен)
read -a words <<<"1 2:3 4:5"
for word in "${words[@]}"; do echo "$word"; done
правильно разбивается на пространство и дает результат:
1
2:3
4:5
при установке IFS на ':'
IFS=':' read -a words <<<"1 2:3 4:5"
for word in "${words[@]}"; do echo "$word"; done
правильно разбивается на ':' и дает результат:
1 2
3 4
5
Тест C-кода
Но выполнение приведенного ниже кода дает один и тот же результат независимо от того, установлена ли переменная среды IFS или нет:
Код С:
#include <stdio.h>
#include <wordexp.h>
#include <stdlib.h>
static void expand(char const *title, char const *str)
{
printf("%s input: %s\n", title, str);
wordexp_t exp;
int rcode = 0;
if ((rcode = wordexp(str, &exp, WRDE_NOCMD)) == 0) {
printf("output:\n");
for (size_t i = 0; i < exp.we_wordc; i++)
printf("%s\n", exp.we_wordv[i]);
wordfree(&exp);
} else {
printf("expand failed %d\n", rcode);
}
}
int main()
{
char const *str = "1 2:3 4:5";
expand("No IFS", str);
int rcode = setenv("IFS", ":", 1);
if ( rcode != 0 ) {
perror("setenv IFS failed: ");
return 1;
}
expand("IFS=':'", str);
return 0;
}
Результат во всех ОС одинаков:
No IFS input: 1 2:3 4:5
output:
1
2:3
4:5
IFS=':' input: 1 2:3 4:5
output:
1
2:3
4:5
Как примечание, приведенный выше фрагмент был создан для этого поста — я провел тест с более сложным кодом, который подтвердил, что переменная среды действительно установлена правильно.
Обзор исходного кода
Я просмотрел исходный код реализации функции wordexp, доступный по адресу https://code.woboq.org/userspace/glibc/posix/wordexp.c.html, и похоже, что он действительно использует $IFS, но, возможно, непоследовательно или, возможно, это ошибка.
В частности:
В теле wordexp, которое начинается с строки 2229, оно получает значение переменной среды IFS и обрабатывает его:
строки 2273 - 2276:
/* Find out what the field separators are.
* There are two types: whitespace and non-whitespace.
*/
ifs = getenv ("IFS");
Но позже в функции, похоже, не используются значения $IFS для разделения слов.
Это выглядит как ошибка, если только разделители полей в строке 2273 и разделители слов в строка 2396 означает разные вещи.
строки 2395 - 2398:
default:
/* Is it a word separator? */
if (strchr (" \t", words[words_offset]) == NULL)
{
Но в любом случае код, похоже, использует только пробел или табуляцию в качестве разделителя, в отличие от bash, который учитывает значения разделителя, заданные IFS.
Вопросы
- Я что-то упустил, и есть ли способ заставить wordexp разбивать символы, кроме пробелов?
- If the split is only on whitespace, is this a bug in the
- gcc library implementation or
- на справочной странице Linux для wordexp, где утверждается, что $IFS можно использовать для определения разделителей
Заранее большое спасибо за все ваши комментарии и идеи!
Резюме ответов и обходной путь
В принятом ответе был намек на то, как добиться разделения непробельных символов из $IFS: вам нужно установить $IFS и поместить строку, которую вы хотите разделить, в качестве значения для временной переменной окружения, а затем вызвать wordexp для этой временной переменной. Это продемонстрировано в обновленном коде ниже.
Хотя такое поведение, видимое в исходном коде, на самом деле может и не быть ошибкой, оно определенно выглядит как сомнительное дизайнерское решение…
Обновленный код:
#include <stdio.h>
#include <wordexp.h>
#include <stdlib.h>
static void expand(char const *title, char const *str)
{
printf("%s input: %s\n", title, str);
wordexp_t exp;
int rcode = 0;
if ((rcode = wordexp(str, &exp, WRDE_NOCMD)) == 0) {
printf("output:\n");
for (size_t i = 0; i < exp.we_wordc; i++)
printf("%s\n", exp.we_wordv[i]);
wordfree(&exp);
} else {
printf("expand failed %d\n", rcode);
}
}
int main()
{
char const *str = "1 2:3 4:5";
expand("No IFS", str);
int rcode = setenv("IFS", ":", 1);
if ( rcode != 0 ) {
perror("setenv IFS failed: ");
return 1;
}
expand("IFS=':'", str);
rcode = setenv("FAKE", str, 1);
if ( rcode != 0 ) {
perror("setenv FAKE failed: ");
return 2;
}
expand("FAKE", "${FAKE}");
return 0;
}
что дает результат:
No IFS input: 1 2:3 4:5
output:
1
2:3
4:5
IFS=':' input: 1 2:3 4:5
output:
1
2:3
4:5
FAKE input: ${FAKE}
output:
1 2
3 4
5
gcc -g -Wall -Wextra
, а также перекомпилировать GCC из исходного кода. , и вы можете использовать Frama-C либо в исходном коде C, либо в исходном коде вашей libc. - person Basile Starynkevitch   schedule 21.01.2021getenv ("IFS")
, чтобы подтвердить, что среда видит вызовsetenv()
— и это так. Ваше использование правильное, если я что-то упустил. Такое же поведение наgcc (GCC) 10.2.0
иgcc (SUSE Linux) 7.4.1
. (очень хорошая запись вопроса) - person David C. Rankin   schedule 21.01.2021wordexp()
не делает то же самое, что встроенныйread
. - person Shawn   schedule 21.01.2021IFS=":"
и вводите что-то на своем терминале, вы все равно вводите неsh:-c:echo 1
, аsh -c 'echo 1'
, разделенные пробелами и табуляцией. IFS влияет на разделение слов, а не на разделение полей. - person KamilCuk   schedule 21.01.2021