in似乎无处不在,但并不是每个系统都会将它放在同一个地方(通常echo
)。在不知道它在哪里的情况下调用它的最安全的方法是什么?coreutils
/bin/echo
echo
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)系统,因此您可以在大多数系统上进行编译coreutils
,echo
但这可能不是您想要的。
另请注意,您会发现 的版本之间存在不兼容性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 -l
,sh
的索拉里斯输出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
实现,例如bash
builtin 与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
要打印的字符串/变量的前面和在正则表达式的前面外部子模式匹配/选择的\( \)
值很重要:前者可以防止您正在打印的值被命令错误地解释expr
为expr
关键字,而后者则确保 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
和空字符串。
您可能还想咨询autoconf
和m4
获取资源,因为我认为这些工具会不遗余力地寻找回声,如果它们找不到有效的printf
或其他有效的东西,它们可以用来进行明确的打印。
从字面上看还有什么
我真诚地认为任何不依赖于你必须用蛮力搜索正确的东西echo
都会是最好的。很有可能特定的程序echo
不会被安装,或者不会安装在你所看到的地方,或者从开始的自动暴力搜索/
会让一些可怜的家伙的系统陷入瘫痪。
虽然可能性很小,但二进制文件可能会通过您的指纹识别为 GNU coreutils
echo
,但会有行为差异:即使 GNU 从未更改其实现,有人可能会包装自己安装的 GNU 版本,echo
以不做他们认为要做的事情这是一种愚蠢的行为(透明地传递所有参数,除了默默地删除特殊的参数,同时设置他们想要的参数在 shell 脚本中是微不足道的,因此您可以轻松地echo --help
打印正确的文本,但echo -e '\055'
会做错误的事情)。不,甚至没有二进制通过彻底的指纹识别是肯定的:我之前已经编辑过原始 ELF 二进制文件来改变行为,并且我会再次这样做。有时它是为了启用非常有用的功能(不是默默地删除包含非 ASCII 字节的消息,例如闭源消息传递软件中的 Unicode 笑脸),有时是为了非常小的事情,例如将PS1
shell 中的硬编码默认值更改为 ,而不是\$\
)\w \$
。我个人没有足够的理由这样做,echo
因为在我实际使用的系统上,我只是忽略了echo
几乎所有严肃的工作,但其他人可能对默认echo
行为的感受和我对默认PS1
变量值的感受一样强烈。所以您回到了功能测试echo
,此时请参阅上面的部分。
另外,请注意,我的系统中 GNU coreutilsecho
安装为gecho
,因此无论是有效搜索PATH
和可能的安装位置,还是仅对名为 的文件进行强力搜索echo
,都不会捕获这些系统。
实际上,我敢打赌,与专门perl
具有 GNU 的系统相比,更多的系统会安装某种脚本语言(例如安装的脚本语言),它可以执行您想要的操作coreutils
echo
:某些脚本语言无处不在,并且大多数都有一种实现或明确定义的规范,而echo
实现则无数并且严格遵循一个规范:“做一些与echo
尽可能多的其他实现略有不同的事情”。