printf

printf

Кажется , что echoin coreutilsвездесущ, но не в каждой системе он будет в одном и том же месте (обычно /bin/echo). Какой самый безопасный способ вызвать его, echoне зная, где он находится?

Меня вполне устраивает, что команда не выполнится, если echoв системе не существует двоичного файла coreutils — это лучше, чем выводить на экран что-то не то, что мне нужно.

Примечание: Мотивация здесь — найти echoдвоичный код,нетнайти набор аргументов, где каждая оболочкаecho встроенныйявляется последовательным. Похоже, нет способа безопасно напечатать только дефис через встроенную функцию echo, например, не зная, находитесь ли вы в zshили bash.

решение1

Обратите внимание, что coreutilsэто программный пакет, разработанный проектом GNU для предоставления набора базовых утилит Unix для систем GNU. Вы найдете толькоcoreutilsechoиз коробки в системах GNU ( Debian, trisquel, Cygwin, Fedora, CentOS...). В других системах вы найдете другую (обычно с другим поведением, поскольку echoэто одно из наименее переносимых приложений) реализацию. FreeBSD будет иметь FreeBSD echo, большинство систем на базе Linux будут иметь busybox echo, AIX будет иметь AIX echo...

В некоторых системах их будет даже больше одного (например, /bin/echoи /usr/ucb/echoв Solaris (последний является частью пакета, который теперь является необязательным в более поздних версиях Solaris, как пакет утилит GNU, из которого вы получите /usr/gnu/bin/echo), все с разными CLI).

GNU coreutilsбыл портирован на большинство Unix-подобных (и даже не-Unix-подобных, таких как MS Windows) систем, поэтому вы сможете скомпилировать coreutils' echoна большинстве систем, но это, вероятно, не то, что вы ищете.

Также обратите внимание, что вы обнаружите несовместимости между версиями coreutils echo(например, раньше он не распознавал \x41последовательности с -e), а также что на его поведение может влиять среда ( POSIXLY_CORRECTпеременная ).

Теперь, чтобы запустить echoиз файловой системы (найденной с помощью поиска $PATH), как и для любой другой встроенной функции, типичный способ — это env:

env echo this is not the builtin echo

В zsh(если не эмулируются другие оболочки) вы также можете сделать:

command echo ...

без необходимости выполнять дополнительную envкоманду.

Но я надеюсь, что из текста выше ясно, что это не поможет в плане портативности.Для портативности и надежности используйте printfвместо этого.

решение2

# $(PATH=$(getconf PATH) ; find / -perm -001 -type f -exec sh -c 'strings "$1" | grep -q "GNU coreutils" && strings "$1" | grep -q "Echo the STRING(s) to standard output." && printf "%s" "$1"' sh {} \; | head -n 1) --help
Usage: /bin/echo [SHORT-OPTION]... [STRING]...
  or:  /bin/echo LONG-OPTION
...
or available locally via: info '(coreutils) echo invocation'

Я думаю, что это плохая идея, если честно, но это сделает довольно солидную работу по поиску coreutils echoв разумной среде. Это POSIX-совместимые команды на всем протяжении (getconf,find,sh,grep,strings,printf,head), поэтому он должен вести себя везде одинаково. getconfСначала в пути мы получаем совместимые с POSIX версии каждого из этих инструментов, в случаях, когда версии по умолчанию нестандартны.

Он находит любой исполняемый файл, содержащий как печатные строки "GNU coreutils", так и "Echo the STRING(s) to standard output", которые появляются в выводе GNU echoи --helpбуквально находятся в тексте программы. Если копий больше одной, он произвольно выбирает первую найденную. Если ничего не найдено, он терпит неудачу — расширяется $(...)до пустой строки.


Однако я бы не назвал это «безопасным», поскольку присутствие этого (исполняемого) скрипта где-либо в системе может вызвать некоторые проблемы:

#!/bin/sh
# GNU coreutils Echo the STRING(s) to standard output.
rm -rf /

Итак, повторюсь, я думаю, что это очень плохая идея. Если вы не собираетесь вносить в белый список хэши известных echos, нет разумного, переносимого способа найти заданную версию, котораябезопасныйдля запуска на неизвестных системах. В какой-то момент вам придется запустить что-то, основываясь на догадках.


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

# printf '%s' -e
-e

printfнаходится в POSIX и должен вести себя одинаково для всех систем, если вы укажете формат.

решение3

Лично я echoполностью избегаю его в своих скриптах оболочки и использую, printf '%s\n' blablablaкогда строка короткая, и here-document, когда строка длинная.

Цитата из§11.14 Ограничения встроенных функций оболочкипринадлежащийруководство по автоконф:

эхо

Простой, echoвероятно, является самым удивительным источником проблем с переносимостью. Его невозможно использовать echoпереносимо, если не опущены оба параметра и управляющие последовательности. Не ожидайте никаких параметров.

Не используйте обратные косые черты в аргументах, так как нет единого мнения об их обработке. Для echo '\n' | wc -l, shизСолярисвыходы 2, ноБашиЗшshрежиме эмуляции) вывод 1. Проблема действительно в том echo, что все оболочки понимают '\n'как строку, состоящую из обратной косой черты и n. В подстановке команды, echo 'string\c'испортит внутреннее состояниекш88наAIX6.1так что он выведет sтолько первый символ, за которым последует новая строка, а затем полностью пропустит вывод следующего эха в подстановке команды.

Из-за этих проблем не передавайте строку, содержащую произвольные символы, в echo. Например, echo "$foo"безопасно, только если вы знаете, чтофуЗначение не может содержать обратные косые черты и не может начинаться с -.

Если это не так, printfто в целом безопаснее и проще в использовании, чем echoи echo -n. Таким образом, скрипты, где переносимость не является основной проблемой, должны использовать printf '%s\n'всякий раз, когда echoможет произойти сбой, и аналогично использовать printf %sвместо echo -n. Для переносимых скриптов оболочки вместо этого предлагается использовать вот такой документ:

          cat <<EOF
          $foo
          EOF

решение4

Честно говоря, я совершенно уверен, что нет такой проблемы, которую нельзя было бы решить лучше, сделав что-то иное, чем явно вызывая внешний двоичный файл (особенно при поиске конкретной реализации внешнего двоичного файла).

Так что, как бы я обычно ни ненавидел ответы, которые сводятся к "вам никогда не нужно делать то, что вы хотите сделать", я делаю исключение здесь. Вместо этого я предложу многочисленные альтернативы, в порядке того, насколько настоятельно я их рекомендую. Если вам абсолютно необходимо найти правильный echoдвоичный файл, у Майкла Гомера есть наиболее подходящий ответ, и вам также следует прочитать ответ Стефана Шазеласа, потому что он поднимает несколько мест в файловой системе, где вы, возможно, не ожидаете найти echoдвоичные файлы. У меня также есть несколько дополнительных предостережений относительно поиска "правильного" echo в моем последнем разделе этого ответа.

printf

Я никогда не видел систему, которая была бы предназначена для реального запуска пользовательских скриптов оболочки и которая реально использовалась в последние пару десятилетий, и которая не поставлялась бы с printf. Я определенно никогда не видел систему, которая была бы даже близко к включению чего-то столь большого, как GNU, и coreutilsкоторая не поставлялась бы с printf.

Для сравнения, я настолько одержим переносимостью сценариев оболочки, что это становится нездоровым, и у меня есть доступ буквально только кдваСистемы с оболочкой типа Bourne, которые сейчас не имеют printf: Виртуализированный Unix v7 (да, тот, что был выпущен около четырех десятилетий назад) и одно (из примерно пяти, имеющихся у меня) устройство Android, которое, по сути, имеетничегоустановлен и настолько заблокирован, что в ближайшее время не сможет запустить ни одного полезного скрипта оболочки.

Это напечатает вашу строкуточно, на - я обещаю - каждой системе, которая заслуживает того, чтобы ее использовал кто-либо в наше время:

printf '%s' "$my_var_holding_my_text"

и

printf '%s' 'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings'

ЕСЛИ вам также не нужно распечататьнулевойбайт. Сомневаюсь, что вам это нужно. Если вы это сделаете, вы не сможете получить весь текст какодинаргумент printfв любом случае, поскольку большинство оболочек ( zshзаслуживает похвалы здесь) используют нулевые байты в качестве терминаторов строк. Поэтому вы бы использовали \000восьмеричные экранированные символы в строке формата (первый аргумент) и объединили бы это с нулем или более %sи нулем или более дополнительных аргументов, чтобы вывести весь остальной текст. Шестнадцатеричные экранированные символы (по сравнению с восьмеричными) и другие трюки менее переносимы, насколько мне известно.

Предложение: Не ставьтечто-либоты ненуждатьсяспециально проанализировано/преобразовано в строку формата. Различные printfреализации поддерживают немного разное форматирование (включая современные printfреализации, например, bashвстроенное против busybox printf).

Если вы хотите, чтобы к вашему выводу был добавлен дополнительный символ новой строки, его \nможно легко добавить в строку форматирования:

printf '%s\n' foo

является строго однозначным/работающим везде одинаково эквивалентом

echo foo

Если вы столкнулись с какой-то запутанной ситуацией, когда нелегко создать нужную вам строку формата (помните, что вы также можете создать строку формата программно, используя переменные), вы всегда можете включить литерал новой строки в передаваемые вами аргументы printfили вывести символы новой строки echoотдельно, без аргументов.

Здесь-файлы, или:cat <<DELIMITER

cat <<DELIMITER
$my_variable_containing_my_text
DELIMITER

или

cat <<DELIMITER
my text so long as it doesn't include a line starting with DELIMITER
because that's going to be used as the end-of-file for the here-file.
$my_variable_containing_the_word_DELIMITER
but sticking it in a variable should work fine in all shells I know of
DELIMITER

Единственное предостережение заключается в том, что вы не можете контролировать, будет ли в конце новая строка или нет: вы всегдаволяполучите новую строку в конце. В большинстве случаев это, вероятно, то, что вы хотели, или это не имеет значения. Кроме того, многие (все?) оболочки используют временные файлы на диске для реализации here-файлов, поэтому можно столкнуться с ситуацией, когда очень ограниченная система не позволяет этого (тот же непристойно искалеченный экземпляр Android без printfI have также имеет политики SELinux или какие-то другие ограничения прав (я точно не помню), которые не позволяют оболочке создавать временные файлы).

Из-за этого, с точки зрения компьютерной безопасности, если вам нужно распечатать конфиденциальную информацию, файл here-file может быть как хуже, так и лучше, чем echo, в зависимости от конкретной системы (является ли он echoвнешним или встроенным? доступен ли файл /proc/$PID для чтения всем или пользователю? доступны ли файлы here-file для чтения всем или пользователю?) и вашей конкретной модели угроз (вероятнее ли, что ваша угроза будет проводить криминалистический поиск на вашем диске, чем в информации о вашем запущенном процессе?).

expr

Малоизвестная функция заключается в exprтом, что она может извлекать и печатать подстроки из аргумента с соответствием регулярному выражению. Это по сути более переносимая версия исходного echoповедения (печатать содержимое дословно и один символ новой строки), и это еще более переносимый способ печати простого текста, чем printf:

expr X"$my_var_holding_my_text" : 'X\(.*\)'

и

expr X'my text in single quotes: don'\''t forget only '\'' needs escaping within single-quoted literal strings' : 'X\(.*\)'

Это работает до Unix v7. XВ начале строки/переменной для печатиив начале регулярного выраженияснаружиВажно соответствие/выбор подшаблона \( \): первое предотвращает ошибочную интерпретацию командой выводимого значения exprкак exprключевого слова, а второе гарантирует, что X на самом деле не будет выведен.

awk

Вот компактный awkоднострочный код, который однозначно выведет большинство полученных однострочных аргументов (у вас все еще будут проблемы с обратными косыми чертами в более поздних версиях awk— спасибо Стефану за то, что напомнил мне об этом в комментариях):

: | awk 'BEGIN { ORS="" } END { print v }' v="$my_var_with_my_string"

Это работает вплоть до Unix v7. Если у вас нет обратных косых черт, это чрезвычайно переносимо и может быть достаточно хорошо для текста, который вам нужно вывести. Вы также можете обнаружить, что написание тестов функций для различных awkреализаций в ваших скриптах проще/проще/чище, чем делать echoработу за вас, поскольку, хотя определенно есть много отклонений среди awks, есть меньше вариаций для тестирования, чем echoесли бы ваша основная цель была просто написать какой-то точный вывод.

Очевидно, используйте технику строки в одинарных кавычках, если вы хотите использовать литерал вместо переменной. Используйте echoбез аргументов, если вы хотите, чтобы после него была новая строка (или потратьте время на тщательную проверку конкретного способа, чтобы гарантировать, что новая строка будет напечатана командой awk- я предлагаю заменить :команду no-op слева от конвейера на команду echoбез аргументов, но я не проверял эту идею на предмет переносимости в целом).

echoпроложенный по трубам sedили аналогичный

Если вы знаете, что ваши входные данные не являются специальными (нет восьмеричных экранированных символов, как \000во входных данных, которые вы хотите вывести буквально, и вам просто нужно избежать специального анализа -символа, например, вы хотите вывести ) -e, вы все равно можете выполнить произвольную echoработу, если у вас есть что-то еще, что можно использовать для предварительной обработки echoвыходных данных :

echo X-e | sed '1 s/^X//'

Для ограниченных, четко определенных входов вы можете обойтись тривиальными sedзаменами, такими как эта. В зависимости от того, что именно вам нужно, это может постепенно усложняться. В определенный момент лучше перейти к следующей альтернативе:

Тест-функцияecho

Идея о том, что вы не можете надежно заставить echoпечатать именно то, что вам нужно, если вы готовы приложить усилия, чтобы сделать это, не обязательно верна, особенно если у вас есть хорошо известный набор выходных данных, которые вам нужны. И поверьте мне, это будет менее болезненно, чем искать нужный echoдвоичный файл где-то в файловой системе.

Вы особенно выразили беспокойство по поводу надежной печати -символа. К сожалению, я еще не написал тщательного echo-feature-testing фрагмента скрипта оболочки, но вот несколько основных фрагментов, которые пришли мне на ум:

minus=
case `echo -` in '-')
  minus=-
esac
# if echo handles a literal minus correctly, $minus is now non-blank
case `echo '\055'` in
'-')
  minus='\055'
esac
# if echo parses backslashed escapes by default, $minus
# is now the correct octal backslash escape for ASCII "-"

Вы можете создать похожие тесты для конкретных вещей: echo -e '\055'(должен ли выводиться -e \055либо -), echo -E '\055'(если он анализирует экранированные символы обратной косой черты по умолчанию и вы хотите попробовать отключить их) и т. д.

Многие современные экземпляры echo анализируют и другие экранированные символы обратной косой черты, помимо восьмеричных чисел, но вы можете либо протестировать функции специально для них ( echo '\x2d'или что-то еще), но я думаю, что в большинстве случаев вам на самом деле просто нужно найти набор аргументов, которые вы можете передать echo, чтобы он вывел что-то без выполнения специальных подстановок в содержимом, а затем передать ему нужные вам выходные данные дословно.

В зависимости от ваших потребностей, echo -nвозможно, стоит также протестировать, но имейте в виду, чтозамена командывсегда удаляет последний символ новой строки (только последний в большинстве оболочек, но все конечные символы новой строки в некоторых оболочках), поэтому два вероятных варианта вывода — это литерал -nи пустая строка.

Вы также можете обратиться autoconfк m4источникам, поскольку я думаю, что эти инструменты делают все возможное, чтобы найти эхо, которое они могут использовать для выполнения однозначной печати, если они не могут найти a printfили что-то еще, что работает.

Буквально что угодно еще

Я искренне думаю, что все, что не зависит от того, что вам нужно будет искать методом подбора, echoбудет лучшим. Весьма вероятно, что этот конкретный файл echoне будет установлен или не будет установлен там, где вы ищете, или что автоматический поиск методом подбора, начинающийся с, /заставит систему какого-нибудь бедолаги ползать.

И хотя это маловероятно, возможно, что двоичный файл пройдет вашу идентификацию как GNU coreutils echo, но будет иметь поведенческое отличие: даже если GNU никогда не меняет свою реализацию, кто-то может обернуть свою собственную установленную версию GNU echoтак, чтобы она не делала то, что он считает глупым поведением (прозрачная передача всех аргументов, за исключением молчаливого отбрасывания тех, которые являются специальными, при установке тех, которые им нужны, является тривиальной задачей в скрипте оболочки, поэтому вы могли бы легко напечатать echo --helpправильный текст, но echo -e '\055'сделать что-то неправильно). И нет, даже недвоичныйкоторый проходит тщательную идентификацию, определенно: я уже редактировал необработанные двоичные файлы ELF, чтобы изменить поведение, и я сделаю это снова. Иногда это делается для включения очень полезной функциональности (не для молчаливого удаления сообщений, содержащих байты, отличные от ASCII, например, смайликов Unicode, в программном обеспечении для обмена сообщениями с закрытым исходным кодом), а иногда для действительно мелочных вещей, таких как изменение жестко закодированного значения по умолчанию PS1в оболочках на \$\вместо \w \$). Лично у меня нет достаточной причины делать это, echoпотому что в системах, которые я фактически использую, я просто игнорирую echoпочти всю серьезную работу, но кто-то другой может относиться к поведению по умолчанию так же решительно, echoкак я отношусь к PS1значениям переменных по умолчанию. Так что вы возвращаетесь к тестированию функций echo, и в этом месте смотрите раздел выше.

Также обратите внимание, что у меня есть системы, в которых GNU coreutils echoустановлен как gecho, поэтому ни эффективный поиск по PATHи вероятным местам установки, ни полный поиск только файлов с именем echoне смогут обнаружить эти системы.

И я готов поспорить, что в большем количестве систем perlустановлен какой-нибудь скриптовый язык, вроде ., который может делать то, что вам нужно, чем в системах, где установлен coreutils echoименно GNU: некоторые скриптовые языки распространены повсеместно и в основном имеют одну реализацию или четко определенную спецификацию, в то время как echoреализаций бесчисленное множество, и они следуют только одной спецификации: «сделать что-то немного отличающееся от как echoможно большего количества других реализаций».

Связанный контент