我希望能够捕获命令替换的确切输出,包括尾随的换行符。
我意识到默认情况下它们会被剥离,因此可能需要进行一些操作才能保留它们,并且我想保留原始退出代码。
例如,给定一个带有可变数量的尾随换行符和退出代码的命令:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Run Code Online (Sandbox Code Playgroud)
我想运行类似的东西:
exact_output f
Run Code Online (Sandbox Code Playgroud)
并有输出:
Output: $'\n\n'
Exit: 5
Run Code Online (Sandbox Code Playgroud)
我对bash
POSIX 和 POSIX都感兴趣sh
。
Sté*_*las 21
获取命令的完整标准输出的常用 ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) 技巧是:
output=$(cmd; ret=$?; echo .; exit "$ret")
ret=$?
output=${output%.}
Run Code Online (Sandbox Code Playgroud)
这个想法是添加一个额外的.\n
. 命令替换只会去掉那个 \n
. 然后你把.
with去掉${output%.}
。
请注意,在除 之外的 shell 中zsh
,如果输出为 NUL 字节,这仍然不起作用。使用yash
,如果输出不是文本,这将不起作用。
另请注意,在某些语言环境中,您在末尾插入的字符很重要。.
一般应该没问题,但其他一些可能不行。例如x
(在其他一些答案中使用)或@
在使用 BIG5、GB18030 或 BIG5HKSCS 字符集的区域设置中不起作用。在这些字符集,所述一定数量的字符的编码端部在相同的字节作为编码x
或@
(0x78,0x40的)
例如,?
在 BIG5HKSCS 中是 0x88 0x78(和x
ASCII 一样是 0x78,系统上的所有字符集对于可移植字符集的所有字符都必须具有相同的编码,包括英文字母@
和.
)。因此,如果cmd
是printf '\x88'
并且我们x
在它之后插入,${output%x}
将无法x
像$output
实际包含?
.
.
如果有任何字符的编码以与 相同的编码结尾,则使用替代可能会导致相同的问题.
,但由于前段时间检查过,我可以说没有任何字符集可用于语言环境中Debian、FreeBSD 或 Solaris 系统有这样的字符,这对我来说已经足够了(以及为什么我决定用.
它来标记英语句子的结尾,所以看起来很合适)。
@Isaac 讨论的更正确的方法是将区域设置更改为 C 仅用于剥离最后一个字符 ( ${output%.}
),这将确保仅剥离一个字节,但这会使代码显着复杂化,并可能引入兼容性问题它自己的。
使用bash
and zsh
,假设输出没有 NUL,您还可以执行以下操作:
IFS= read -rd '' output < <(cmd)
Run Code Online (Sandbox Code Playgroud)
要获取 的退出状态cmd
,您可以执行wait "$!"; ret=$?
inbash
但不能执行 in zsh
。
为了完整起见,注意rc
/ es
/akanga
有操作员。在它们中,命令替换,表示为`cmd
(或`{cmd}
更复杂的命令)返回一个列表($ifs
默认情况下,通过在, space-tab-newline上拆分)。在这些 shell 中(与类似 Bourne 的 shell 相对),换行符的剥离仅作为$ifs
拆分的一部分完成。因此,您可以清空$ifs
或使用``(seps){cmd}
指定分隔符的表单:
ifs = ''; output = `cmd
Run Code Online (Sandbox Code Playgroud)
或者:
output = ``()cmd
Run Code Online (Sandbox Code Playgroud)
在任何情况下,命令的退出状态都会丢失。您需要将它嵌入到输出中并在之后提取它,这会变得很难看。
在fish 中,命令替换是带有(cmd)
子shell 的,并且不涉及子shell。
set var (cmd)
Run Code Online (Sandbox Code Playgroud)
创建一个$var
数组,其中cmd
if的输出中的所有行都$IFS
为非空,或者if的输出cmd
最多删除一个(与大多数其他 shell 中的所有行相反)换行符 if$IFS
为空。
所以还是有一个问题,在(printf 'a\nb')
和(printf 'a\nb\n')
扩展到同样的事情,即使空$IFS
。
为了解决这个问题,我能想到的最好的方法是:
function exact_output
set -l IFS . # non-empty IFS
set -l ret
set -l lines (
cmd
set ret $status
echo
)
set -g output ''
set -l line
test (count $lines) -le 1; or for line in $lines[1..-2]
set output $output$line\n
end
set output $output$lines[-1]
return $ret
end
Run Code Online (Sandbox Code Playgroud)
另一种方法是:
read -z output < (begin; cmd; set ret $status; end | psub)
Run Code Online (Sandbox Code Playgroud)
Bourne shell 既不支持$(...)
表单也不支持${var%pattern}
运算符,因此很难在那里实现。一种方法是使用 eval 和引用:
eval "
output='`
exec 4>&1
ret=\`
exec 3>&1 >&4 4>&-
(cmd 3>&-; echo \"\$?\" >&3; printf \"'\") |
awk 3>&- -v RS=\\\\' -v ORS= -v b='\\\\\\\\' '
NR > 1 {print RS b RS RS}; {print}; END {print RS}'
\`
echo \";ret=\$ret\"
`"
Run Code Online (Sandbox Code Playgroud)
在这里,我们正在生成一个
output='output of cmd
with the single quotes escaped as '\''
';ret=X
Run Code Online (Sandbox Code Playgroud)
要传递给eval
. 至于 POSIX 方法,如果'
可以在其他字符的末尾找到编码的字符之一,我们就会遇到问题(更糟糕的问题,因为它会成为命令注入漏洞),但值得庆幸的是,例如.
,它不是其中之一,并且引用技术通常是引用 shell 代码的任何内容所使用的技术(请注意,\
存在问题,因此不应使用(也不包括"..."
在其中您需要对某些字符使用反斜杠) . 在这里,我们只在 a 之后使用它,'
这是可以的)。
(不考虑退出状态,您可以通过将其保存在临时文件中(echo $status > $tempfile:q
在命令之后)来解决该问题)
小智 5
对于新问题,此脚本有效:
\n#!/bin/bash\n\nf() { for i in $(seq "$((RANDOM % 3 ))"); do\n echo;\n done; return $((RANDOM % 256));\n }\n\nexact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );\n unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL\n LC_ALL=C ; out=${out%x};\n unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL\n printf \'Output:%10q\\nExit :%2s\\n\' "${out}" "$?"\n }\n\nexact_output f\necho Done\n
Run Code Online (Sandbox Code Playgroud)\n执行时:
\nOutput:$\'\\n\\n\\n\'\nExit :25\nDone\n
Run Code Online (Sandbox Code Playgroud)\n较长的描述
\nPOSIX shell 处理删除的通常智慧\\n
是:
\n\n添加一个
\nx
s=$(printf "%s" "${1}x"); s=${s%?}\n
Run Code Online (Sandbox Code Playgroud)\n这是必需的,因为根据POSIX 规范,最后一个新行 ( S ) 被命令扩展删除了:
\n\n\n在替换结束时删除一个或多个字符的序列。
\n
x
.在这个问题中有人说过, anx
可能与某些编码中某些字符的尾随字节混淆。但是,我们如何猜测某种语言中某种可能的编码中哪个或哪个字符更好,至少可以说,这是一个困难的命题。
然而; 这根本就是不正确的。
\n我们需要遵循的唯一规则是准确添加我们删除的内容。
\n应该很容易理解,如果我们向现有字符串(或字节序列)添加一些内容,然后我们删除完全相同的内容,则原始字符串(或字节序列)必须相同。
\n我们哪里出了问题?当我们混合 字符和字节时。
\n如果我们添加一个字节,我们必须删除一个字节,如果我们添加一个字符,我们必须删除完全相同的字符。
\n第二个选项,添加一个字符(然后删除完全相同的字符)可能会变得令人费解和复杂,并且,是的,代码页和编码可能会妨碍。
\n然而,第一个选项是很有可能的,并且在解释之后,它会变得非常简单。
\n让我们添加一个字节,一个 ASCII 字节 (<127),并为了尽可能减少复杂性,假设在 az 范围内有一个 ASCII 字符。或者正如我们应该说的,十六进制范围内的一个字节0x61
- 0x7a
。让我们选择其中任何一个,也许是一个x(实际上是一个字节的值0x78
)。我们可以通过将 x 连接到字符串来添加这样的字节(假设是\xc3\xa9
):
$ a=\xc3\xa9\n$ b=${a}x\n
Run Code Online (Sandbox Code Playgroud)\n如果我们将字符串视为字节序列,我们会看到:
\n$ printf \'%s\' "$b" | od -vAn -tx1c\n c3 a9 78\n 303 251 x\n
Run Code Online (Sandbox Code Playgroud)\n以 x 结尾的字符串序列。
\n如果我们删除 x(字节值0x78
),我们会得到:
$ printf \'%s\' "${b%x}" | od -vAn -tx1c\n c3 a9\n 303 251\n
Run Code Online (Sandbox Code Playgroud)\n它工作没有问题。
\n假设我们感兴趣的字符串以 byte 结尾0xc3
:
$ a=$\'\\x61\\x20\\x74\\x65\\x73\\x74\\x20\\x73\\x74\\x72\\x69\\x6e\\x67\\x20\\xc3\'\n
Run Code Online (Sandbox Code Playgroud)\n让我们添加一个字节的值0xa9
$ b=$a$\'\\xa9\'\n
Run Code Online (Sandbox Code Playgroud)\n现在字符串变成了这样:
\n$ echo "$b"\na test string \xc3\xa9\n
Run Code Online (Sandbox Code Playgroud)\n正是我想要的,最后两个字节是utf8 中的一个字符(因此任何人都可以在他们的 utf8 控制台中重现这一结果)。
\n如果我们删除一个字符,原始字符串就会改变。但这不是我们添加的,我们添加了一个字节值,它恰好写成 x,但无论如何都是一个字节。
\n我们需要避免将字节误解为字符。我们需要的是删除我们使用的字节的操作0xa9
。事实上,ash、bash、lksh 和 mksh 似乎都是这样做的:
$ c=$\'\\xa9\'\n$ echo ${b%$c} | od -vAn -tx1c\n 61 20 74 65 73 74 20 73 74 72 69 6e 67 20 c3 0a\n a t e s t s t r i n g 303 \\n\n
Run Code Online (Sandbox Code Playgroud)\n但不是 ksh 或 zsh。
\n然而,这很容易解决,让所有这些 shell 执行字节删除:
\n$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c \n
Run Code Online (Sandbox Code Playgroud)\n就是这样,所有测试过的 shell 都可以工作(yash 除外)(对于字符串的最后一部分):
\nash : s t r i n g 303 \\n\ndash : s t r i n g 303 \\n\nzsh/sh : s t r i n g 303 \\n\nb203sh : s t r i n g 303 \\n\nb204sh : s t r i n g 303 \\n\nb205sh : s t r i n g 303 \\n\nb30sh : s t r i n g 303 \\n\nb32sh : s t r i n g 303 \\n\nb41sh : s t r i n g 303 \\n\nb42sh : s t r i n g 303 \\n\nb43sh : s t r i n g 303 \\n\nb44sh : s t r i n g 303 \\n\nlksh : s t r i n g 303 \\n\nmksh : s t r i n g 303 \\n\nksh93 : s t r i n g 303 \\n\nattsh : s t r i n g 303 \\n\nzsh/ksh : s t r i n g 303 \\n\nzsh : s t r i n g 303 \\n\n
Run Code Online (Sandbox Code Playgroud)\n就这么简单,告诉 shell 删除 LC_ALL=C 字符,该字符恰好是从0x00
到 的所有字节值的一个字节0xff
。
请注意,某些 shell 不支持在运行时更改区域设置(尽管 POSIX 要求这样做)。
\n虽然上面的代码应该适用于任何(除换行符或空)字节作为哨兵值,但它可以变得更容易,而无需更改区域设置:
\n使用.
or通常/
应该没问题,因为 POSIX 要求:
<period>
、<slash>
、<newline>
和关联的编码值<carriage-return>
在实现支持的所有语言环境中保持不变。\xe2\x80\x9d,这意味着这些值在任何语言环境/编码中都将具有相同的二进制表示形式。<period>
、<slash>
、<newline>
和的字节值<carriage-return>
不得作为任何语言环境中任何其他字符的一部分出现。\xe2\x80\x9d,这意味着上述情况不会发生,因为没有部分字节序列可以由这些字节/字符补全为任何语言环境/编码中的有效字符。\n(请参阅6.1 可移植字符集)上述内容不适用于可移植字符集的其他字符。
\n对于评论中讨论的示例,一种可能的解决方案(在 zsh 中失败)是:
\n#!/bin/bash\n\nLC_ALL=zh_HK.big5hkscs\n\na=$(printf \'\\210\\170\');\nb=$(printf \'\\170\');\n\nunset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL\nLC_ALL=C ; a=${a%"$b"};\nunset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL\n\nprintf \'%s\' "$a" | od -vAn -c\n
Run Code Online (Sandbox Code Playgroud)\n这将消除编码问题。
\n 归档时间: |
|
查看次数: |
3342 次 |
最近记录: |