Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert carriage return (\r) to actual overwrite

Questions

Is there a way to convert the carriage returns to actual overwrite in a string so that 000000000000\r1010 is transformed to 101000000000?

Context

1. Initial objective:

Having a number x (between 0 and 255) in base 10, I want to convert this number in base 2, add trailing zeros to get a 12-digits long binary representation, generate 12 different numbers (each of them made of the last n digits in base 2, with n between 1 and 12) and print the base 10 representation of these 12 numbers.

2. Example:

  1. With x = 10
  2. Base 2 is 1010
  3. With trailing zeros 101000000000
  4. Extract the 12 "leading" numbers: 1, 10, 101, 1010, 10100, 101000, ...
  5. Convert to base 10: 1, 2, 5, 10, 20, 40, ...

3. What I have done (it does not work):

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. Why it does not work

Because the variable x_base2_padded contains the whole sequence 000000000000\r1010. This can be confirmed using hexdump for instance. In the for loop, when I extract the first 12 characters, I only get zeros.

5. Alternatives

I know I can find alternative by literally adding zeros to the variable as follow:

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

Or by padding with zeros using printf and rev

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

Although these alternatives solve my problem now and let me continue my work, it does not really answer my question.

Related issue

The same problem may be observed in different contexts. For instance if one tries to concatenate multiple strings containing carriage returns. The result may be hard to predict.

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

The first echo will output foo. Although you might expect the other echo to output foofoofoo..., they all output foobar.

like image 364
Slagt Avatar asked Jun 05 '20 14:06

Slagt


Video Answer


2 Answers

The following function overwrite transforms its argument such that after each carriage return \r the beginning of the string is actually overwritten:

overwrite() {
    local segment result=
    while IFS= read -rd $'\r' segment; do
       result="$segment${result:${#segment}}"
    done < <(printf '%s\r' "$@")
    printf %s "$result"
}

Example

$ overwrite $'abcdef\r0123\rxy'
xy23ef

Note that the printed string is actually xy23ef, unlike echo $'abcdef\r0123\rxy' which only seems to print the same string, but still prints \r which is then interpreted by your terminal such that the result looks the same. You can confirm this with 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

The function overwrite also supports overwriting by arguments instead of \r-delimited segments:

$ overwrite abcdef 0123 xy
xy23ef

To convert variables in-place, use a subshell: myvar=$(overwrite "$myvar")

like image 172
Socowi Avatar answered Sep 21 '22 14:09

Socowi


With awk, you'd set the field delimiter to \r and iterate through fields printing only the visible portions of them.

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 ""
}'

This is indeed too long to put into a command substitution. So you better wrap this in a function, and pipe the lines to be resolved to that.

like image 24
oguz ismail Avatar answered Sep 25 '22 14:09

oguz ismail