对于新问题,此脚本有效:
#!/bin/bash
f() { for i in $(seq "$((RANDOM % 3 ))"); do
echo;
done; return $((RANDOM % 256));
}
exact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; out=${out%x};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf 'Output:%10q\nExit :%2s\n' "${out}" "$?"
}
exact_output f
echo Done
执行时:
Output:$'\n\n\n'
Exit :25
Done
较长的描述
POSIX shell处理删除的通常的智慧\n是:
添加一个 x
s=$(printf "%s" "${1}x"); s=${s%?}
这是必需的,因为根据POSIX规范,命令扩展会删除最后一个新行(S):
在替换结束时删除一个或多个字符的序列。
关于尾随x。
在这个问题中有人说过,“ an” x可以与某种编码中某个字符的尾随字节混淆。但是,我们将如何猜测以某种可能的编码在某种语言中哪个或哪个字符更好,这至少可以说是一个困难的命题。
然而; 那简直是不对的。
我们需要遵循的唯一规则是准确添加要删除的内容。
容易理解,如果我们在现有字符串(或字节序列)中添加内容,然后又删除完全相同的内容,则原始字符串(或字节序列)必须相同。
我们哪里出错了?当我们混合 字符和字节时。
如果添加一个字节,则必须删除一个字节,如果添加一个字符,则必须删除完全相同的字符。
第二种选择是添加一个字符(并随后删除完全相同的字符),这可能会令人费解和复杂,并且可以,代码页和编码可能会受到干扰。
但是,第一种选择是完全有可能的,并且在解释了它之后,它将变得很简单。
让我们添加一个字节,一个ASCII字节(<127),并尽可能减少混乱,让我们假设az范围内的ASCII字符。或者,正如我们应该说的那样,十六进制范围0x61- 中的一个字节0x7a。让我们选择其中的任何一个,也许是x(实际上是value的一个字节0x78)。我们可以通过将x连接到字符串来添加这样的字节(假设为é):
$ a=é
$ b=${a}x
如果将字符串视为字节序列,则会看到:
$ printf '%s' "$b" | od -vAn -tx1c
c3 a9 78
303 251 x
以x结尾的字符串序列。
如果删除该x(字节值0x78),则会得到:
$ printf '%s' "${b%x}" | od -vAn -tx1c
c3 a9
303 251
它可以正常工作。
一个更困难的例子。
假设我们感兴趣的字符串以byte结尾0xc3:
$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'
并添加一个值字节 0xa9
$ b=$a$'\xa9'
字符串现在变成了这个:
$ echo "$b"
a test string é
正是我想要的,后两个字节是utf8 中的一个字符(因此任何人都可以在其utf8控制台中重现此结果)。
如果我们删除一个字符,则原始字符串将被更改。但这不是我们添加的内容,而是添加了一个字节值,该值恰好被写为x,但无论如何还是一个字节。
我们需要避免将字节误解为字符。我们需要一个删除我们使用的字节的动作0xa9。实际上,ash,bash,lksh和mksh似乎都可以做到这一点:
$ c=$'\xa9'
$ echo ${b%$c} | od -vAn -tx1c
61 20 74 65 73 74 20 73 74 72 69 6e 67 20 c3 0a
a t e s t s t r i n g 303 \n
但不是ksh或zsh。
但是,这很容易解决,让我们告诉所有这些shell进行字节删除:
$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c
就是这样,所有的shell测试工作(yash除外)(对于字符串的最后一部分):
ash : s t r i n g 303 \n
dash : s t r i n g 303 \n
zsh/sh : s t r i n g 303 \n
b203sh : s t r i n g 303 \n
b204sh : s t r i n g 303 \n
b205sh : s t r i n g 303 \n
b30sh : s t r i n g 303 \n
b32sh : s t r i n g 303 \n
b41sh : s t r i n g 303 \n
b42sh : s t r i n g 303 \n
b43sh : s t r i n g 303 \n
b44sh : s t r i n g 303 \n
lksh : s t r i n g 303 \n
mksh : s t r i n g 303 \n
ksh93 : s t r i n g 303 \n
attsh : s t r i n g 303 \n
zsh/ksh : s t r i n g 303 \n
zsh : s t r i n g 303 \n
就是这么简单,告诉外壳删除LC_ALL = C字符,对于从0x00to的所有字节值,该字符正好是一个字节0xff。
评论解决方案:
对于注释中讨论的示例,一种可能的解决方案(在zsh中失败)是:
#!/bin/bash
LC_ALL=zh_HK.big5hkscs
a=$(printf '\210\170');
b=$(printf '\170');
unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; a=${a%"$b"};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
printf '%s' "$a" | od -vAn -c
这将消除编码问题。
$IFS,因此不会被捕获为参数。