2

The recommended pattern for bash as far as I know is to always quote the shell variables.
E.g. "$VAR" instead of $VAR.
But how can I achieve the same safety the quotes provide for variables meant to be interpreted remotely?
E.g in the following:

ssh server.com<<CODE
TARGET="target dir"
COUNT= \$( ls /foo/bar/\$TARGET | wc -l )  
echo \$COUNT > count.txt    

CODE   

For the code to work I need to escape $COUNT and $TARGET.
But how do I achieve the same safety that the "$COUNT" or "$TARGET" provides for this specific case?

Update
I have pasted only the part that is problematic.
I have also other lines that the variables are defined outside of the heredoc so if I use <<'CODE' then the snippet breaks.
For a more complete example:

SOME_STRING="SOME VALUE"  
ssh server.com<<CODE  
echo $SOME_VALUE > test_file.txt  # <--- does not work if I use <<'CODE'
TARGET="target dir"
COUNT= \$( ls /foo/bar/\$TARGET | wc -l )  
echo \$COUNT > count.txt    

CODE   
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
Jim
  • 10,120

2 Answers2

1

It looks like here-docs leave double quotes in place. For example, consider the following command:

cat <<HEREDOC
"this will still be quoted"
HEREDOC

This will produce the following quoted string as its output:

"this will still be quoted:

So you should just be able to quote your variables as usual:

SOME_STRING="SOME VALUE"
ssh server.com<<CODE
echo "$SOME_VALUE" > test_file.txt
TARGET="target dir"
COUNT="\$( ls /foo/bar/\$TARGET | wc -l )"
echo "\$COUNT" > count.txt
CODE

Regarding quoting variables inside command substitutions, refer to the following post: Quoting within $(command substitution) in Bash

Note that I've copied your snippet verbatim, but it looks like you probably meant to use the variable SOME_STRING instead of SOME_VALUE. Here is a slightly modified version of your snippet that I can run locally with the expected results:

mkdir -p "/foo/bar/target dir"

SOME_STRING="SOME VALUE"
ssh localhost<<CODE
echo "$SOME_STRING" > test_file.txt
TARGET="target dir"
COUNT="\$( ls "/foo/bar/\$TARGET" | wc -l )"
echo "\$COUNT" > count.txt
CODE
igal
  • 9,886
  • The command substitution should be quoted? And what about the $TARGET in the command substitution? That is unquoted in your code – Jim Oct 20 '17 at 13:11
  • Tried that. Breaks the script – Jim Oct 20 '17 at 13:13
  • Yes, you would probably want to quote $TARGET as well. But my point is that the quoting isn't being affected by the here-doc, so there isn't a difference between the local and remote quoting in this situation. Whether or not you want to quote the command substitution or anything else is up to you. A general rule of thumb is to quote everything that you want treated as a single token. – igal Oct 20 '17 at 13:18
  • The snippet does not compile – Jim Oct 20 '17 at 13:19
  • Compile? You mean doesn't run as expected? – igal Oct 20 '17 at 13:22
  • What error are you getting? Also, I would suggest rewriting your example so that someone else can run it themselves as-is. – igal Oct 20 '17 at 13:28
0

We seem to get variations on this question a lot over the last few days.

In this case, quote the here-document:

ssh server.com <<'CODE'
TARGET="target dir"
COUNT= $( ls /foo/bar/$TARGET | wc -l )  
echo $COUNT > count.txt    
then  
fi  
CODE  

The 'CODE' (as opposed to just CODE) will ensure that the here-document is passed to the utility as it is written, without expanding variables or performing command substitutions.

Note that you also have syntax errors in the here-document (spaces around = and no if ta match the then). You should also double-quote the expansions both $COUNT and $TARGET as usual.

ssh server.com <<'CODE'
TARGET="target dir"
COUNT=$( ls "/foo/bar/$TARGET" | wc -l )  
printf '%s\n' "$COUNT" >count.txt    
CODE 

If you are trying to count the number of files in a remote directory, use this instead:

ssh server 'find "/path/to/dir" -maxdepth 1 -type f -exec echo . \;' | wc -l

This will correctly count the number of files in /path/to/dir.

To write the result to a remote file:

ssh server 'find "/path/to/dir" -maxdepth 1 -type f -exec echo . \; | wc -l >file'

If the code that you need to execute remotely is very complex, then it may be better to create a script on the server to run it. The variable that you need to pass to the script can be passed on its command line:

ssh server ./script.sh "$SOME_VARIABLE"

The script:

#!/bin/sh

VAR="$1"

# do stuff
Kusalananda
  • 333,661
  • The reason I am not using singe quotes around the CODE is because the rest of the commands are not interpreted correctly. Without quotes it works – Jim Oct 20 '17 at 12:44
  • E.g. if I have the line echo $SOME_VAR > ~/test_file.txt if I use <<'CODE then the file is empty i.e. nothing is printed. And SOME_VAR is defined outside of the here doc – Jim Oct 20 '17 at 12:57
  • I updated post too – Jim Oct 20 '17 at 12:59
  • I have more code and the logic is more complex than just counting files. I just pasted the part that shows the issue – Jim Oct 20 '17 at 13:00
  • @Jim See updated end of my answer. – Kusalananda Oct 20 '17 at 13:06
  • Very reasonable suggestion about another script. To be honest though it is not that complicated at this point to not just find a good trade off to what I am trying to do. I am interested to know if it is possible to have a good script the way I describe or is impossible and I need to create another script as you mentioned – Jim Oct 20 '17 at 13:08