Почему make игнорирует escape-последовательность, если вывод передается по конвейеру?

В Makefile я хочу напечатать байт, представленный в виде шестнадцатеричного числа, и передать его в STDIN другой программы. По какой-то причине это не работает:

without-pipe:
    @printf '\x66\x6f\x6f'

with-bash-and-pipe:
    @/bin/bash -c "printf '\x66\x6f\x6f' | cat"

with-pipe:
    @printf '\x66\x6f\x6f' | cat

Запуск этого файла производит:

$ make without-pipe 
foo

$ make with-bash-and-pipe 
foo

$ make with-pipe 
\x66\x6f\x6f

Какая особенность make я пропускаю и как правильно заставить последнюю цель произвести тот же результат. Тот самый with-bash-and-pipe это своего рода обходной путь.

1 ответ

Решение

Примечание: мой тестовый стенд - Ubuntu 18.04.2 LTS.


Несколько шагов требуется, чтобы понять поведение.

1. Сделать несколько умнее

Это выглядит как make определяет, требуется ли оболочка. я сделал

strace -f -e execve make without-pipe

и часть вывода была:

[pid 17526] execve("/usr/local/sbin/printf", ["printf", "\\x66\\x6f\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)
[pid 17526] execve("/usr/local/bin/printf", ["printf", "\\x66\\x6f\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)
[pid 17526] execve("/usr/sbin/printf", ["printf", "\\x66\\x6f\\x6f"], 0x5569b5a5b440 /* 67 vars */) = -1 ENOENT (No such file or directory)
[pid 17526] execve("/usr/bin/printf", ["printf", "\\x66\\x6f\\x6f"], 0x5569b5a5b440 /* 67 vars */) = 0

Так что в этом случае make просто исследует возможные места printf (в соответствии с $PATH) пока инструмент не найден.

Поведение отличается в последнем случае. Эта команда

strace -f -e execve make with-pipe

урожайность (среди других линий)

[pid 17592] execve("/bin/sh", ["/bin/sh", "-c", "printf '\\x66\\x6f\\x6f' | cat"], 0x561465476440 /* 67 vars */) = 0

Инструмент достаточно умен, чтобы сказать, что вы хотите запустить трубу. Если вы делаете, оболочка используется. Оболочка sh,

2. В трех случаях используются разные printf реализации

  • Как показано выше, make without-pipe использования printf исполняемый файл доступен в ОС.

  • printf является встроенным в Bash (подтвердите с type -a printf), так make with-bash-and-pipe использует встроенный из Bash.

  • printf также встроенный в sh (подтвердите с (unset PATH; printf 'printf works\n'; ls); если бы он не был встроенным, sh не смог бы найти исполняемый файл, как он не может найти ls), так make with-pipe использует встроенный из ш.

3. printf не требуется, чтобы понять \x66 и тому подобное

POSIX спецификацияprintf говорит:

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

[...]

Ни один из двух связанных документов не говорит \xHH и такие должны быть особенными. Я не уверен, что спецификация явно позволяет им быть особенными, но на самом деле они особенные для некоторых printfв вопросе.

Но не для printf встроенный в ш.


Как правильно заставить последнюю цель производить тот же результат? Тот самый with-bash-and-pipe это своего рода обходной путь.

Вы можете убедиться printf вы запускаете не встроенную, указав полный путь:

/usr/bin/printf '\x66\x6f\x6f' | cat

однако это требует, чтобы вы знали путь заранее. Чтобы исправить эту проблему, вы можете использовать env:

env printf '\x66\x6f\x6f' | cat

env запустит исполняемый файл, найденный в $PATH, Вы можете ожидать env присутствовать, потому что это требуется POSIX.

С другой стороны, в общем, вы не можете быть уверены, что любой printf исполняемый (встроенный или нет) поддерживает \xHH на первом месте. Поэтому реальное исправление состоит в том, чтобы переписать вашу строку формата так, чтобы она правильно понималась всеми POSIX-совместимыми реализациями printf, Это кажется полезным:

В дополнение к escape-последовательностям, показанным в обозначении формата файла XBD (\\, \a, \b, \f, \n, \r, \t, \v), \dddгде ddd представляет собой однозначное, двухзначное или трехзначное восьмеричное число, записывается в виде байта с числовым значением, указанным восьмеричным числом.

Пример:

printf '\146\157\157'

Этот подход работает во всех трех случаях.

Другие вопросы по тегам