bashvariables

How to remove a newline from a string in Bash


I have the following variable.

echo "|$COMMAND|"

which returns

|
REBOOT|

How can I remove that first newline?


Solution

  • In Bash the 'substring replacement' feature will allow you to remove newline characters

    Note that this feature is an ANSI C bashism. It may not be available on other shells and is non-posix compliant.

    In Bash it is a concise alternative to the slower tr command (see below for performance comparison).

    It takes the general form:

    ${parameter/pattern/string}

    For more details see the section: Substring Replacement on tldp.org

    For example:

    COMMAND=$'\nREBOOT\r   \n'
    echo "|${COMMAND}|"
    |
       OOT
    |
    
    echo "|${COMMAND//[$'\t\r\n']}|"
    |REBOOT   |
    
    echo "|${COMMAND//[$'\t\r\n ']}|"
    |REBOOT|
    

    See Parameter Expansion and ANSI C standard in bash's man page:

    man bash | sed '/^$/{ x;\|parameter/pattern/string|{s/^\n*//;p};s/.*//;h;};H;d' 
    
         ${parameter/pattern/string}
         ${parameter//pattern/string}
         ${parameter/#pattern/string}
         ${parameter/%pattern/string}
                Pattern  substitution.  The pattern is expanded to produce a
                pattern just as in pathname  expansion.   Parameter  is  ex‐
                panded and the longest match of pattern against its value is
                replaced with string.  string undergoes tilde expansion, pa‐
                rameter  and  variable expansion, arithmetic expansion, com‐
                mand and process substitution, and quote removal.  The match
                is  performed using the rules described under Pattern Match‐
                ing below.  In the first form above, only the first match is
                replaced.  If there are two slashes separating parameter and
                pattern (the second form above), all matches of pattern  are
                replaced  with  string.   If  pattern  is preceded by # (the
                third form above), it must match at the beginning of the ex‐
                panded value of parameter.  If pattern is preceded by % (the
                fourth form above), it must match at the end of the expanded
                value  of  parameter.   If  the expansion of string is null,
                matches of pattern are deleted.  If string is null,  matches
                of  pattern  are  deleted and the / following pattern may be
                omitted.
    
    man bash | sed '/^$/{ x;/ANSI \+C \+standard/{s/^\n*//;p};s/.*//;h;};H;d'
    man bash | sed '/^$/{ x;/ANSI[[:space:]]\+C[[:space:]]\+standard/{s/^\n*//;p};s/.*//;h;};H;d'
    
         Character  sequences of the form $'string' are treated as a special
         variant of single quotes.  The sequence  expands  to  string,  with
         backslash-escaped characters in string replaced as specified by the
         ANSI C standard.  Backslash escape sequences, if present,  are  de‐
         coded as follows:
                \a     alert (bell)
                \b     backspace
                \e
                \E     an escape character
                \f     form feed
                \n     new line
                \r     carriage return
                \t     horizontal tab
                \v     vertical tab
                \\     backslash
                \'     single quote
                \"     double quote
                \?     question mark
                \nnn   the  eight-bit  character  whose  value  is the octal
                       value nnn (one to three octal digits)
                \xHH   the eight-bit character whose value is the  hexadeci‐
                       mal value HH (one or two hex digits)
                \uHHHH the  Unicode (ISO/IEC 10646) character whose value is
                       the hexadecimal value HHHH (one to four hex digits)
                \UHHHHHHHH
                       the Unicode (ISO/IEC 10646) character whose value  is
                       the hexadecimal value HHHHHHHH (one to eight hex dig‐
                       its)
                \cx    a control-x character
    

    Further...

    As asked by @AlexJordan, this will suppress all specified characters. So what if $COMMAND do contain spaces...

    COMMAND=$'         \n        RE BOOT      \r           \n'
    echo "|$COMMAND|"
    |
               BOOT      
    |
    
    read -r COMMAND <<<"${COMMAND//[$'\t\r\n']}"
    echo "|$COMMAND|"
    |RE BOOT|
    

    Explanation

    Answering Vulwsztyn's question:

    Why does this work when the pattern is empty?

    In ${COMMAND//[$'\t\r\n ']} :

    1 step more further

    If you try to replace nothing by something, for sample two consecutive spaces (then you could add two more spaces after replaced string in order to balance output):

    echo "|${COMMAND//*()/  }  |"
    |  R  E     B  O  O  T  |
    

    Avoid fork to tr for single string!

    Let compare:

    COMMAND=$'\nREBOOT\r   \n'
    echo ${COMMAND@Q}
    $'\nREBOOT\r \n'
    
    COMMAND=$(echo $COMMAND|tr -d '\n\t\r ')
    echo ${COMMAND@Q}
    'REBOOT'
    

    Then

    time for i in {1..1000};do
        COMMAND=$'\nREBOOT\r   \n'
        COMMAND=$(echo $COMMAND|tr -d '\n\t\r ')
    done;echo ${COMMAND@Q}
    
    real    0m2.785s
    user    0m2.296s
    sys     0m0.774s
    'REBOOT'
    

    With

    COMMAND=$'\nREBOOT\r   \n'
    COMMAND="${COMMAND//[$'\t\r\n ']}"
    echo ${COMMAND@Q}
    
    time for i in {1..1000};do
        COMMAND=$'\nREBOOT\r   \n'
        COMMAND="${COMMAND//[$'\t\r\n ']}"
    done;echo ${COMMAND@Q}
    
    real    0m0.006s
    user    0m0.001s
    sys     0m0.004s
    'REBOOT'
    

    Doing 1'000 forks to tr take more than 2700ms on my host, while same job is done in 6ms ( 464.2x faster!! ) by using built-in bash Parameter Expansion!!

    Note: In fact: var=$(echo | tr x y) implie two forks, not only one! By using following syntax, you will avoid 1 (x1000) fork, so this could be a little quicker:

    time for i in {1..1000};do
        COMMAND=$'\nREBOOT\r   \n'
        COMMAND=$( tr -d '\n\t\r ' <<<"$COMMAND" )
    done;echo ${COMMAND@Q}
    
    real    0m2.181s
    user    0m1.590s
    sys     0m0.566s
    'REBOOT'
    

    But still a lot overkill compared to the pure bash method.