Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a bash heredoc put its result directly into a variable?

Tags:

bash

I have some code like this:

CMD=$(cat <<EOC
docker run 
   -p $MY_IP:$LOCAL_PORT:$LOCAL_PORT -p $MY_IP:$PEER_PORT:$PEER_PORT 
   -v $CERT_DIR:/cert 
   $ETCD_IMAGE 
   --name $MACHINE.$DOMAIN 
   --peer-cert-file=/cert/server-cert.pem
   --peer-key-file=/cert/server-key.pem --peer-ca-file=/cert/ca.pem 
   --peer-addr=$MY_IP:$PEER_PORT 
   --peers=$OIPPC
EOC
)

is there a way to get a here doc to assign result directly to variable in bash without intervening process (cat)? This code works, it just feels like too much work.

like image 591
Greg Avatar asked Dec 14 '22 16:12

Greg


1 Answers

How to put a here-string into a variable in Bash:

In Bash use read with the -d delimiter set to null:

IFS= read -r -d '' cmd <<EOC
    ...blah blah...
EOC

Make sure you really use IFS= like shown, in front of read, otherwise any leading and trailing spaces will be trimmed. Make sure you use -r otherwise some backslashes would be understood as escape backslashes.

Some would argue that it's simpler to just use a plain assignment as:

cmd='
    ...blah blah...
'

But sometimes you have lots of quotes to the point that it becomes simpler and nicer to use this.

Subtle note. With this, read returns a failure return code (1) since the null-byte delimiter is not read before EOF. While this is alright most of the times, it can be a problem if you're using set -e (but you really shouldn't use set -e anyway). If you want to be sure, add:

IFS= read -r -d '' cmd <<EOC || true
    ...blah blah...
EOC

Now, seriously, about your problem.

Below is a serious note that you really should take into account: don't put code into strings! it's broken!. Instead, use a function or (still bad, but not broken) an array. Here's how you would use an array:

mycommand=(
    docker run 
        -p "$MY_IP:$LOCAL_PORT:$LOCAL_PORT"
        -p "$MY_IP:$PEER_PORT:$PEER_PORT"
        -v "$CERT_DIR":/cert
        "$ETCD_IMAGE"
        --name "$MACHINE.$DOMAIN"
        --peer-cert-file=/cert/server-cert.pem
        --peer-key-file=/cert/server-key.pem
        --peer-ca-file=/cert/ca.pem 
        --peer-addr="$MY_IP:$PEER_PORT"
        --peers="$OIPPC"
)

(observe the quotes that I took time to type, with love). Then you can safely run it (by safely I mean it's all right if you're having glob characters or quotes or spaces in your arguments) as:

"${mycommand[@]}"

(observe the healthy quotes, again). If you want to print the command, use this:

printf '%s\n' "${mycommand[*]}"

Unfortunately, the line breaks won't be preserved here. But really, that shouldn't be a problem at all. If really needed, you should pass this command through a formatter of some sort (well, very likely it doesn't exist so you'll have to code it yourself). But put the things in the right order: you want to define a command, to execute it (and, optionally format it, for user display), not the other way round, have a string that's nice to the user's eyes that you then have to parse (dangerously) to transform into code.

like image 115
gniourf_gniourf Avatar answered Jan 11 '23 23:01

gniourf_gniourf