Search code examples
stringbashline-endings

Bash: Can't append a string to an existing string - it appears to overwrite the start of that string instead


This is a script that helps us in creating a dhcpd.conf file.

sample inputs (ex. mac-tab-;-tab-IP)

DC:D3:21:75:61:90   ;   10.25.131.17
    ;   

expected outputs

Host 27-48 { hardware ethernet DC:D3:21:75:61:90 ; fixed-address 10.25.131.17 ; }

#host 27-48 { hardware ethernet ; fixed-address ; }

Currently the line outputted is this:

Host 27-48 { hardware ethernet 00:16:6B:C8:3D:C9 ; fixed-address 10.25.129.185

Specific line in code I'm stuck on

outputLine="Host $((names[i]))-$((startingNumber+counter)) { hardware ethernet $first ; fixed-address $second"

If I try adding ; }

outputLine="Host $((names[i]))-$((startingNumber+counter)) { hardware ethernet $first ; fixed-address $second ; }"

I get this:

; } 27-48 { hardware ethernet 00:16:6B:C8:3D:C9 ; fixed-address 10.25.129.185

The issue is, whenever I append " ; }" to the end of the above line, it overwrites the beginning of the line. I've tried a few tricks to work around it, such as writing the above line to a string, and then trying to append to the string, but the same issue occurs. I had an idea to export the entire contents to a file, and re-reload the file into an array just so I can append, but it seems a little overkill.

for ((j=1; j<=${sizes[i]}; j++ )); do

    #split line, read split as two entries for an arrIN
    IN=(${line[counter+1]})
    arrIN=(${IN//;/ })
    first="${arrIN[0]}"
    second=${arrIN[1]}


    if [ ${lineSize[counter+1]} -gt 5 ]
    then
        #sed 's/$/ ; }/' $outputLine > newoutputLine
        outputLine="Host $((names[i]))-$((startingNumber+counter)) { hardware ethernet $first ; fixed-address $second"
        echo $outputLine
    else
        echo "#host $((names[i])) $((startingNumber+counter)) { hardware ethernet ; fixed-address ; }"
    fi
    counter=$((counter+1))
done

Solution

  • As ruakh explains in a comment on the question, the problem was a CR (0xD, \r) character at the end of the value of variable $second, which can be removed with the following parameter expansion: second="${second//$'\r'/}".

    The rest of this answer explains the symptom and provides background information.


    The issue is, whenever I append " ; }" to the end of the above line, it overwrites the beginning of the line.

    "overwriting the beginning of a line" almost always points to an embedded CR (0xD, \r) character, which, when a string is printed to the terminal, gives the appearance of overwriting the start of the line:

    $ printf 'abc\rd'  # `printf '\r'` produces a CR 
    dbc  # It LOOKS LIKE 'd' replaced 'a', but that's an artifact of printing to the terminal.
    

    It is only because the terminal interprets the CR (\r) as "place the cursor at the start of the line" that d - the remaining string after \r - appears to "overwrite" the start of the already-printed part of the string.

    You can visualize embedded CRs (and other control characters) with cat -et, which represents them in caret notation as ^M:

    $ printf 'abc\rd' | cat -et
    abc^Md   # ^M represents a CR (\r)
    

    As you can see, the d didn't actually overwrite a, the start of the string.


    CR (\r) instances are rarely used in the world of Unix text processing. If they do appear as part of text data, it is usually from Windows-originated sources that use CRLF (\r\n) line endings rather than the Unix LF-only (\n) line endings.

    Often, the simplest approach is to simply remove the CR (\r) instances before using the data with Unix utilities.

    There are many, many existing answers that cover this territory, often recommending use of third-party utility dos2unix, easily installable via the package managers of many platforms (e.g., sudo apt install dos2unix on Ubunutu, or brew install dos2unix on macOS, via Homebrew).

    Alternatively,

    • for text already stored in a variable, use ruakh's approach based on parameter expansion; e.g., second="${second//$'\r'/}"

    • for files, solutions using standard utilities can act as a makeshift dos2unix.