2

I know this isn't a good thing to do and/or a good solution but I was just messing around trying to answer this question and I came across the following problem.

The goal is to read the input file character by character and if the character is within the specified range, replace it.

Input:

NNNNN
NNNNN
NNNNN
NNNNN

Script:

#!/bin/bash
#set -x

input=~/tmp/in
declare -i count=0
low=$1
high=$2
new_char=$3

while IFS=$'\n' read -r line; do
    while IFS= read -rn1 char; do
        if ((count>=low)) && ((count<=high)); then
            printf '%s' "$new_char"
        else
            printf '%s' "$char"
        fi
        ((count++))
    done <<<"$(printf '%s' "$line")"
    echo
done <"$input"

Desired output (when run as: ./script.sh 10 13 P):

NNNNN
NNNNP
PPPNN
NNNNN

Actual output:

$ ./script.sh 10 13 P
NNNNN
NNNNPP
PPNNN
NNNNN

I'm not sure why it's seemingly creating a new character out of thin air on the 2nd line and not replacing the 13th character on the 3rd line. I've tried moving the ((count++)) statement to the beginning of the second while loop, I've tried starting at count=1, I've tried seemingly every combination of ((++count)) || count=$((count+1)) || ((count+1)). With varying results but all seem to add an extra character on the second line.

I've double checked that the input file has no trailing whitespace on any of the lines.

  • 1
    count must be 1 at the beginning or put ((count++)) at the beginning of the inner loop. Then if you use done < <(printf '%s' "$line") instead of done <<<"$(printf '%s' "$line")", it works as intended. I don't know why yet... – pLumo Jul 11 at 14:21
  • 3
    I think <<<"string" creates a temp file in the background, and bash is not stripping the trailing newline like command substitution does. – glenn jackman Jul 11 at 14:27
  • If you run with set -x, you will see a printf %s '' at the end of every line except for on the line that becomes too long. This possibly means that your inner read returns an empty string at the end of each line. – Kusalananda Jul 11 at 14:30
3

Here Strings seem to produce a newline at the end:

You can try and see the difference:

cat <<<"test"
cat <(printf '%s' "test")

So, you should use < <(printf '%s' "$line") instead of <<<"$(printf '%s' "$line")".

2

I would write:

while IFS= read -r line; do
    for ((i=0; i<${#line}; i++)); do
        char=${line:i:1}
        ((count++))
        ((low <= count && count <= high)) && char=$new_char
        printf '%s' "$char"
    done
    echo
done <"$input"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.