将回车符 (\r) 转换为实际的覆盖。

5

问题

有没有一种方法可以将字符串中的回车符转换为实际的覆盖符,使得 000000000000\r1010 转换为 101000000000

背景

1. 初始目标:

给定一个十进制数 x(介于 0 和 255 之间),我想将此数转换为二进制,添加尾随零以获得一个 12 位数字的二进制表示形式,生成 12 个不同的数字(每个数字由二进制中最后的 n 个数字组成,其中 n 在 1 和 12 之间),并打印这 12 个数字的十进制表示形式。

2. 示例:

  1. 对于 x = 10
  2. 二进制表示为 1010
  3. 添加尾随零得到 101000000000
  4. 提取前 12 个数字:1, 10, 101, 1010, 10100, 101000, ...
  5. 转换为十进制:1, 2, 5, 10, 20, 40, ...

3. 我所做的(它不起作用):

x=10
x_base2="$(echo "obase=2;ibase=10;${x}" | bc)"
x_base2_padded="$(printf '%012d\r%s' 0 "${x_base2}")"
for i in {1..12}
do
    t=$(echo ${x_base2_padded:0:${i}})
    echo "obase=10;ibase=2;${t}" | bc
done

4. 为什么不起作用

因为变量x_base2_padded包含整个序列000000000000\r1010,可以使用hexdump进行确认。在for循环中,当我提取前12个字符时,只得到了零。

5. 可选方案

我知道我可以通过在变量中添加零来找到替代方法,如下所示:

x_base2=1010
x_base2_padded="$(printf '%s%0.*d' "${x_base2}" $((12-${#x_base2})) 0)"

或者使用printfrev函数在末尾填充零。

x_base2=1010
x_base2_padded="$(printf '%012s' "$(printf "${x_base2}" | rev)" | rev)"

虽然这些替代方案现在解决了我的问题,让我能够继续工作,但并没有真正回答我的问题。

相关问题

同样的问题可能会出现在不同的情境中。例如,如果尝试连接包含回车符的多个字符串,则结果可能难以预测。

str=$'bar\rfoo'
echo "${str}"
echo "${str}${str}"
echo "${str}${str}${str}"
echo "${str}${str}${str}${str}"
echo "${str}${str}${str}${str}${str}"

第一个echo会输出foo。尽管你可能期望另一个echo输出foofoofoo...,但它们都输出foobar


1
问题实际上只是关于如何存储包含回车符的字符串所打印出来的实际结果。"初始目标"部分旨在防止评论问"你真正想做什么?"。我确实提供了相当多的上下文,以确保每个人都清楚明白。 "问题"部分对我来说似乎非常清晰,这就是我真正要问的问题,如果过多的细节会让人困惑,那我很抱歉。 - Slagt
@alecxs 谢谢。但是如果你提取前12个字符,你只会得到零 echo -e ${x:0:12}。即使你在中间存储到一个变量中,\r仍然存在。 - Slagt
@Slagt 感谢您确认这一点。也许一旦您接受了一个答案,您可以将问题简化为“将 \r 转换为实际覆盖”或类似的内容。从未见过像您这样写得如此好的问题 - 让我们在“关闭为 XY 问题”的危险消失后使其更加美好 :) - Socowi
3个回答

6
以下函数overwrite会改变其参数,使得在每个回车符\r之后,字符串的开头实际上被覆盖:
overwrite() {
    local segment result=
    while IFS= read -rd $'\r' segment; do
       result="$segment${result:${#segment}}"
    done < <(printf '%s\r' "$@")
    printf %s "$result"
}

示例

$ overwrite $'abcdef\r0123\rxy'
xy23ef

请注意,打印的字符串实际上是xy23ef,而不像echo $'abcdef\r0123\rxy'那样,它只是看起来打印相同的字符串,但仍然打印\r,这被终端解释为结果看起来相同。您可以使用hexdump确认此情况:
$ echo $'abcdef\r0123\rxy' | hexdump -c
0000000   a   b   c   d   e   f  \r   0   1   2   3  \r   x   y  \n
000000f
$ overwrite $'abcdef\r0123\rxy' | hexdump -c
0000000   x   y   2   3   e   f
0000006

overwrite 函数还支持通过参数进行覆盖,而非使用 \r 分隔的段落。

$ overwrite abcdef 0123 xy
xy23ef

要进行原地变量转换,请使用子shell:myvar=$(overwrite "$myvar")

1
非常完美,即使我没有明确要求,它也可以处理多个\r。这也是目前为止建议的最快执行方式。谢谢! - Slagt

3
使用 awk,您需要将字段分隔符设置为 \r 并遍历字段,仅打印它们的可见部分。
awk -F'\r' '{
  offset = 1
  for (i=NF; i>0; i--) {
    if (offset <= length($i)) {
      printf "%s", substr($i, offset)
      offset = length($i) + 1
    }
  }
  print ""
}'

这确实太长了,不适合放在命令替换中。因此你最好将其包装在一个函数中,并将需要解决的行管道传输到该函数中。


与我对已接受答案的评论相同。非常好用,即使有多个\r也可以正常工作,这虽然不是特别要求,但绝对是一个加分项。尽管速度略慢于已接受的答案(+10%),还是非常感谢! - Slagt

1
为了回答这个具体问题,如何将000000000000\r1010转换为101000000000,请参考Socowi的答案
然而,我不会一开始就引入回车符,而是像这样解决问题:
#!/usr/bin/env bash

x=$1

# Start with 12 zeroes
var='000000000000'

# Convert input to binary
binary=$(bc <<< "obase = 2; $x")

# Rightpad with zeroes: ${#binary} is the number of characters in $binary,
# and ${var:x} removes the first x characters from $var
var=$binary${var:${#binary}}

# Print 12 substrings, convert to decimal: ${var:0:i} extracts the first
# i characters from $var, and $((x#$var)) interprets $var in base x
for ((i = 1; i <= ${#var}; ++i)); do
    echo "$((2#${var:0:i}))"
done

1
@alecxs 我对此有一个(不正确的)答案,修正后将与Socowi的答案相同,因此我转而参考该答案。我认为这种方法本质上存在缺陷,因此我专注于解决整体问题的解决方案。 - Benjamin W.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接