
Кажется , что echo
in 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 /
Итак, повторюсь, я думаю, что это очень плохая идея. Если вы не собираетесь вносить в белый список хэши известных echo
s, нет разумного, переносимого способа найти заданную версию, котораябезопасныйдля запуска на неизвестных системах. В какой-то момент вам придется запустить что-то, основываясь на догадках.
Я бы посоветовал вамиспользуйте 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 без printf
I 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
работу за вас, поскольку, хотя определенно есть много отклонений среди awk
s, есть меньше вариаций для тестирования, чем 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
можно большего количества других реализаций».