printf

printf

in似乎无处不在,但并不是每个系统都会将它放在同一个地方(通常echo)。在不知道它在哪里的情况下调用它的最安全的方法是什么?coreutils/bin/echoecho

echo如果系统上不存在coreutils 二进制文件,我对命令失败感到满意- 这比回显与我想要的不同的东西要好。

注意:这里的动机是找到echo二进制文件,不是找到一组参数,其中每个 shell 的echo 内置是一致的。例如,似乎没有一种方法可以通过内置的 echo 安全地仅打印连字符,而不知道您是否在zsh或中bash

答案1

请注意,这coreutils是由 GNU 项目开发的软件包,旨在为 GNU 系统提供一组 Unix 基本实用程序。你只会发现核心工具echo在 GNU 系统上开箱即用(Debian, trisquel, Cygwin, Fedora, CentOS...)。在其他系统上,您会发现不同的实现(通常具有不同的行为,这echo是可移植性最差的应用程序之一)。 FreeBSD 将有 FreeBSD echo,大多数基于 Linux 的系统将有 busybox echo,AIX 将有 AIX echo...

有些系统甚至会有多个(例如Solaris 上的/bin/echo和 )/usr/ucb/echo(后一个是软件包的一部分,在更高版本的 Solaris 中现在是可选的,例如 for GNU 实用程序软件包,您可以从中获得/usr/gnu/bin/echo),所有这些都具有不同的 CLI)。

GNUcoreutils已被移植到大多数类 Unix(甚至非类 Unix,如 MS Windows)系统,因此您可以在大多数系统上进行编译coreutilsecho但这可能不是您想要的。

另请注意,您会发现 的版本之间存在不兼容性coreutils echo(例如,它过去无法识别\x41的序列-e),并且其行为可能会受到环境(POSIXLY_CORRECT变量)的影响。

echo现在,要从文件系统(通过查找找到)运行$PATH,与其他所有内置程序一样,典型的方法是env

env echo this is not the builtin echo

zsh(当不模拟其他 shell 时),您还可以执行以下操作:

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'

老实说,我认为这是一个坏主意,但这将echo在合理的环境中找到 coreutils 方面做得相当扎实。这些都是 POSIX 兼容的命令(getconf,find,sh,grep,strings,printf,head),所以它在任何地方都应该表现相同。getconf在默认版本非标准的情况下,它首先为我们提供了路径中每个工具的 POSIX 兼容版本。

它查找包含可打印字符串“GNU coreutils”和“将字符串回显到标准输出”的任何可执行文件,这些字符串出现在 GNUecho--help输出中并且字面意思是在程序文本中。如果有多个副本,它会任意选择找到的第一个副本。如果没有找到,则失败 -$(...)扩展为空字符串。


然而,我不会称其为“安全”,因为系统上任何地方存在此(可执行)脚本都会给您带来一些麻烦:

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

所以重申一下,我认为这是一个非常糟糕的主意。除非您要将已知的哈希值列入白名单echo,否则没有合理的、可移植的方法来查找它的给定版本安全的在未知系统上运行。在某些时候,你将不得不根据猜测来运行一些东西。


我鼓励你使用printf命令代替,它接受格式和您想要按字面意思使用的任何参数。

# printf '%s' -e
-e

printf在 POSIX 中,如果您提供格式,则所有系统的行为方式应该相同。

答案3

就我个人而言,我echo完全避免在 shell 脚本中使用,printf '%s\n' blablabla当字符串很短时使用,当字符串很长时使用here-document。

引用自§11.14 Shell 内置函数的限制自动配置手册

回声

简单echo可能是可移植性问题最令人惊讶的根源。echo除非选项和转义序列都被省略,否则不可能便携使用。不要指望有任何选择。

不要在参数中使用反斜杠,因为对它们的处理没有达成共识。对于echo '\n' | wc -lsh索拉里斯输出2,但是重击兹什(在sh仿真模式下)输出1。问题确实存在echo:所有 shell 都将其理解'\n'为由反斜杠和 组成的字符串n。在命令替换中,echo 'string\c'会弄乱内部状态克什88操作系统6.1这样它将s仅打印第一个字符,后跟换行符,然后完全删除命令替换中下一个回显的输出。

由于这些问题,请勿将包含任意字符的字符串传递给echo.例如,echo "$foo"只有当您知道这一点时才是安全的的值不能包含反斜杠并且不能以 开头-

如果这可能不是真的,printf那么通常比echo和更安全、更容易使用echo -n。因此,可移植性不是主要问题的脚本应该printf '%s\n'在可能失败时使用echo,并且类似地使用printf %s而不是echo -n.对于可移植 shell 脚本,建议使用如下所示的此处文档:

          cat <<EOF
          $foo
          EOF

答案4

老实说,我相当有信心,除了显式调用外部二进制文件(特别是寻找外部二进制文件的特定实现)之外,没有什么问题不能通过执行其他操作来更好地解决。

因此,尽管我通常讨厌归结为“你永远不需要做你想做的事情”的答案,但我在这里破例。相反,我会按照我建议的强烈程度,提出多种替代方案。如果您绝对必须找到正确的echo二进制文件,Michael Homer 有最合适的答案,您也应该阅读 Stéphane Chazelas 的答案,因为它会在文件系统中显示您可能不希望找到echo二进制文件的多个位置。在本答案的最后一部分中,我还有一些关于搜索“正确”回声的额外警告。

printf

我从未见过一个系统旨在实际运行自定义 shell 脚本,并且在过去几十年中得到了真正的使用,但它不附带printf.我当然从未见过一个系统能够包含像 GNU 这样大的东西,coreutils但它却没有printf开箱即用。

从长远来看,我对 shell 脚本的可移植性非常着迷,而且我只能从字面上访问目前具有类似 Bourne shell 的系统没有printf:虚拟化 Unix v7(是的,大约是四十年前的那个),以及一台(我拥有的大约五台)Android 设备,该设备基本上具有没有什么已安装并且已被锁定,无论如何,短期内都不会运行任何有用的 shell 脚本。

这将打印你的字符串确切地,关于 - 我保证 - 每个值得现代任何人使用的系统:

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 的参数反正,因为大多数 shell(zsh这里值得赞扬)使用空字节作为字符串终止符。因此,您可以\000在格式字符串(第一个参数)中使用八进制转义符,并将其与零个或多个%s以及零个或多个其他参数结合起来以打印所有其他文本。据我所知,十六进制转义(相对于八进制)和其他技巧的可移植性较差。

建议:不要放任何事物你不需要特别解析/转换为格式字符串。不同的printf实现支持略有不同的格式(包括现代printf实现,例如bashbuiltin 与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

需要注意的是,你无法控制最后是否换行:你总是将要最后得到一个换行符。大多数时候,这可能是您想要的,或者并不重要。另外,许多(所有?)shell 使用磁盘上的临时文件来实现此处文件,因此可能会遇到非常受限制的系统不允许这样做的情况(相同的严重瘫痪的 Android 实例,但printf我没有也有 SELinux策略或其他一些权限限制(我记不太清楚了)会阻止 shell 创建临时文件)。

因此,在计算机安全说明中,如果您需要打印敏感信息,则此处文件可能比 更差或更好echo,具体取决于确切的系统(是echo外部系统还是内置系统?是 /proc/$PID世界或用户可读?这里的文件是用户还是世界可读?),以及您的确切威胁模型(您的威胁是否更有可能对您的磁盘进行取证搜索,而不是您正在运行的进程信息?)

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要打印的字符串/变量的前面在正则表达式的前面外部子模式匹配/选择的\( \)值很重要:前者可以防止您正在打印的值被命令错误地解释exprexpr关键字,而后者则确保 X 实际上没有被打印。

awk

这是一个紧凑的awk单行代码,它将明确地打印它收到的大多数单字符串参数(在最新版本的反斜杠上您仍然会遇到问题awk- 感谢 Stephan 在评论中提醒我这一点):

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

这可以追溯到 Unix v7。如果您没有反斜杠,那么这是非常可移植的,并且可能足以满足您需要输出的文本。您可能还会发现awk,在脚本中为不同的实现编写功能测试比echo为您工作更容易/更简单/更干净,因为虽然之间肯定存在很多偏差,但与您的核心目标只是编写一些功能awk相比,需要测试的变化要少。echo准确的输出。

如果您想使用文字而不是变量,显然可以使用单引号字符串技术。如果您想在其后添加换行符,请执行echo不带参数的操作(或者花时间严格审查特定方法以确保命令打印换行符awk- 我建议将:管道左侧的无操作命令替换为echo不带参数,但我还没有仔细审核这个想法的全面可移植性)..

echo通过管道sed或类似方式

如果您知道您的输入并不特殊(没有反斜杠八进制转义符,就像\000您想要按字面打印的输入一样,并且您需要避免专门解析-字符,例如,您想要打印-e,您仍然可以echo为如果您还有其他可以用来预处理echo的输出:

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

对于有限的、定义明确的输入,您可能可以通过sed像这样的简单替换来摆脱困境。根据您的具体需求,它可能会变得越来越困难。在某个时刻,最好转向下一个替代方案:

功能测试echo

echo如果您愿意费尽心思去做的话,您就无法可靠地打印出您想要的东西,这种想法不一定是正确的,特别是如果您有一组众所周知的所需输出。相信我,这echo比在文件系统中的某个位置搜索正确的二进制文件要轻松得多。

您特别表达了对可靠打印字符的担忧-。不幸的是,我还没有编写完整的echo功能测试 shell 脚本片段,但这里有一些我脑海中浮现的基本片段:

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可能也值得测试,但请记住命令替换总是删除最后一个换行符(在大多数 shell 上只是最后一个换行符,但在某些 shell 上所有尾随换行符),因此两个可能的输出选项是文字-n和空字符串。

您可能还想咨询autoconfm4获取资源,因为我认为这些工具会不遗余力地寻找回声,如果它们找不到有效的printf或其他有效的东西,它们可以用来进行明确的打印。

从字面上看还有什么

我真诚地认为任何不依赖于你必须用蛮力搜索正确的东西echo都会是最好的。很有可能特定的程序echo不会被安装,或者不会安装在你所看到的地方,或者从开始的自动暴力搜索/会让一些可怜的家伙的系统陷入瘫痪。

虽然可能性很小,但二进制文件可能会通过您的指纹识别为 GNU coreutils echo,但会有行为差异:即使 GNU 从未更改其实现,有人可能会包装自己安装的 GNU 版本,echo以不做他们认为要做的事情这是一种愚蠢的行为(透明地传递所有参数,除了默默地删除特殊的参数,同时设置他们想要的参数在 shell 脚本中是微不足道的,因此您可以轻松地echo --help打印正确的文本,但echo -e '\055'会做错误的事情)。不,甚至没有二进制通过彻底的指纹识别是肯定的:我之前已经编辑过原始 ELF 二进制文件来改变行为,并且我会再次这样做。有时它是为了启用非常有用的功能(不是默默地删除包含非 ASCII 字节的消息,例如闭源消息传递软件中的 Unicode 笑脸),有时是为了非常小的事情,例如将PS1shell 中的硬编码默认值更改为 ,而不是\$\\w \$。我个人没有足够的理由这样做,echo因为在我实际使用的系统上,我只是忽略了echo几乎所有严肃的工作,但其他人可能对默认echo行为的感受和我对默认PS1变量值的感受一样强烈。所以您回到了功能测试echo,此时请参阅上面的部分。

另外,请注意,我的系统中 GNU coreutilsecho安装为gecho,因此无论是有效搜索PATH和可能的安装位置,还是仅对名为 的文件进行强力搜索echo,都不会捕获这些系统。

实际上,我敢打赌,与专门perl具有 GNU 的系统相比,更多的系统会安装某种脚本语言(例如安装的脚本语言),它可以执行您想要的操作coreutils echo:某些脚本语言无处不在,并且大多数都有一种实现或明确定义的规范,而echo实现则无数并且严格遵循一个规范:“做一些与echo尽可能多的其他实现略有不同的事情”。

相关内容