Linux expand dollar sign in single quotes

I am trying to get a dollar sign variable to be expanded in single quotes. Not sure what I am doing wrong. I have tried every way I can think of.

   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled "${j}" ';
      done
    done
    
   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled "$j" ';
      done
    done
    
   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled \$j ';
      done
    done
    
   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled \\$j ';
      done
    done
    
   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled \\\$j ';
      done
    done
    
   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled \\\\$j ';
      done
    done

The single quotes around the ssh parameter prevent expansion. Try using a here document -

ssh <<!
remotenode  "$j"
!

Ensure dollar variable to be expanded is outside of single quotes eg:

echo 'File not found in '"$PWD"', please re-enter'

In your script you would do:

   for i in `cat file1` 
      do 
        for j in `cat file2`
        do
         ssh $i 'systemctl is-enabled '"$j";
      done
    done
2 Likes

Why single quotes anyway? They stop interpretation of the string quoted. If you want to pass something that changes, how about one of these:-

ssh $i "systemctl is-enabled ${j}";               # No quotes around the variable being passed
ssh $i "systemctl is-enabled '${j}'";             # Wraps single quotes around variable being passed, interpreted locally but not interpreted again remotely
ssh $i "systemctl is-enabled \"${j}\"";           # Wraps double quotes around variable being passed, interpreted locally but could then be interpreted again remotely if the variable is a remote variable name or subshell

The loops you have are rather badly structure too. I know that ssh will gobble up your input file with a normal while read line; do ssh user@server 'printf "Hello world\n"'; done < input_file but you can open multiple input files to help you. Would you consider:-

while read -u11 outer_variable                                            # Read from file descriptor 11
do
   while read -u12 inner_variable                                         # Read from file descriptor 12
   do
      ssh "${user}@${outer_variable}" "foo bar \"${inner_variable}\""
   done 12< "${inner_variable_input_file}"                                # Supply input file on file descriptor 12
done 11< "${outer_variable_input_file}"                                   # Supply input file on file descriptor 11

Obviously it's better to use meaningful variable names. It avoids confusion from re-using them, breaking something that calls your code but has the same names and means you can understand your code when you come back to it many years later. Using i or j might save a few characters typing, but will be worse in the long run.

I hope that these help,
Robin

2 Likes

A few comments allowed?
The way you programmed your script might not be the best use of resources:

for i in `cat file1`                            # useless use of cat, may fail on spaces in file1's lines, `...` deprecated
  do 
  for j in `cat file2`                          # same as above; plus: opens, reads, closes file2 linecount1 times
    do
    ssh $i 'systemctl is-enabled "${j}" ';      # creates and deletes a remote network login (linecount1 * linecount2) times
    done
  done

How about trying, given your shell offers

  • the mapfile command
  • "here documents",

and using

  • a while instead of a for loop
  • the $(...) for "command substitution" in lieu of the `...`

reading each file only once, and connecting to each node in file1 just once (only lightly tested, YMMV):

mapfile -t j < file2
while read i
  do    ssh ${i}  <<-EOFSSH
        $(for X in ${!j[@]}
            do echo "systemctl is-enabled ${j[X]}"
            done)
        EOFSSH
   done <  file1

Please report back...

EDIT: or, if your shell offers "here strings", using "parameter expansion / Pattern substitution", try

mapfile -t j < file2
IFS=$'\n'
while read i
  do    ssh -T ${i}  <<< "${j
[*]/#/systemctl is-enabled }"
  done <  file1

Don't forget to reset IFS to its original value.

2 Likes