Search code examples
bashshellprintfshposix

printf is "eating" the backslashes. How to preserve them?


I'm trying to make a tiny POSIX-compatible sed-like function. However I noticed that the code below is "eating" the backslashes.

input.txt for mysed ./input.txt "TEST2" "TEST2=\"two\"" call :

TEST1="\\_PR.CP%02d"
TEST2="one"
TEST3="\\_PR.P%03d"

Expected:

TEST1="\\_PR.CP%02d"
TEST2="two"
TEST3="\\_PR.P%03d"

Received:

TEST1="\_PR.CP%02d"
TEST2="two"
TEST3="\_PR.P%03d"

How to modify the code below to preserve all the backslashes? Maybe it's possible to replace a custom printf '%b\n' format by something better?

#!/bin/sh
# Replaces the lines containing the text '$2' of a file '$1' with a line '$3'.
mysed () {
    if [ -f "$1" ] ; then
        mysed_tmp="./.mysed"
        rm -f $mysed_tmp
        while IFS= read -r line
        do
            case $line in
                *"$2"*)
                    if [ ! -z "$3" ] ; then
                        printf '%b\n' "$3" >> $mysed_tmp
                    fi
                    ;;
                *)
                    printf '%b\n' "$line" >> $mysed_tmp
                    ;;
            esac
        done < "$1"
        mv $mysed_tmp $1
        return 0
    else
        return 1
    fi
}

echo "TEST1=\"\\\\_PR.CP%02d\"" > input.txt
echo "TEST2=\"one\"" >> input.txt
echo "TEST3=\"\\\\_PR.P%03d\"" >> input.txt

mysed ./input.txt "TEST2" "TEST2=\"two\""
cat ./input.txt

EDIT: replaced '%b\n' with '%s\n', everything is working now as intended.


Solution

  • Look:

    $ x="TEST2=\"two\""; printf '%s\n' "$x"
    TEST2="two"
    
    $ x='TEST2=\"two\"'; printf '%s\n' "$x"
    TEST2=\"two\"
    
    $ TEST1="\\_PR.CP%02d"; printf '%s\n' "$TEST1"
    \_PR.CP%02d
    
    $ TEST1='\\_PR.CP%02d'; printf '%s\n' "$TEST1"
    \\_PR.CP%02d
    

    Hopefully this will make things clearer:

    $ foo() { printf '%%b:<%b>\n%%s:<%s>\n---\n' "$1" "$1"; }
    
    $ foo "TEST2=\\"two\\""
    %b:<TEST2=      wo\>
    %s:<TEST2=\two\>
    ---
    
    $ foo 'TEST2=\\"two\\"'
    %b:<TEST2=\"two\">
    %s:<TEST2=\\"two\\">
    ---
    

    So:

    1. If you don't want backslashes stripped by the shell when the string is defined then don't tell the shell to interpret them - use ' not ".
    2. If you don't want backslashes stripped by printf when the string is printed then don't tell printf to interpret them - use %s not %b.