0

How can we execute a loop of screens, each of them executing a loop so that it picks both inner and outer variables?

Consider this:

# does not print i
for i in 1 2; do
     screen -dmS screen-${i} bash -c 'for j in 1 2; do
          echo screen-${i}-iter-${j}
     done; exec bash'
done

This one does not work because the non-interpolating quotes imply the variables inside are intrinsic to bash -c, right? So ${i} is empty.

Consider also:

# does not print j
for i in 1 2; do
     screen -dmS screen-${i} bash -c "for j in 1 2; do
          echo screen-${i}-iter-${j}
     done; exec bash"
done

The second one also does not work because the interpolating quotes imply interpolation outside of bash -c. So ${j} is empty.

How can I get around this issue? Is it more appropriate to do it in another language?

I tried Python but could not get around the screens dying when the python script completed. So I couldn't see what is in the screen. Anyway, the screen has to stay open once whoever is calling it dies. My attempt in python follows for completeness.

import subprocess

def screen(i): proc = subprocess.Popen('screen -S screen-'+str(i), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE) command = f"for j in 1 2; do\necho screen-{i}-iter-${{j}}\ndone; exec bash\n" proc.stdin.write('{}'.format(command).encode('utf-8'))

for i in range(2): screen(i)

  • You can concatenate strings quoted differently: "screen-${i}"'-iter-${j}'. Here ${i} is double-quoted, ${j} is single-quoted. Is this what you want to achieve? – Kamil Maciorowski Jan 18 '24 at 10:09
  • Yes, this works very well. I'll accept this if you put it in an answer. I can post an answer if you don't feel like doing it. ```bash for i in 1 2; do screen -dmS screen-${i} bash -c "for j in 1 2; do echo screen-${i}"'-iter-${j}'" done; exec bash" done
    
    
    – superAnnoyingUser Jan 18 '24 at 12:09
  • I feel a bit dumb now. – superAnnoyingUser Jan 18 '24 at 12:09

1 Answers1

0

You can concatenate strings quoted differently (or not quoted at all). E.g. in "screen-${i}-"iter'-${j}' there is double-quoted screen-${i}-, unquoted iter and single quoted -${j}. This way you can tell the shell to expand ${i} but not ${j}. If the value of $i is 1 then the resulting word will be screen-1-iter-${j}.

In your particular case where $i takes numerical values and $IFS is the default one, just mixing the quotes will work.

However, with bash -c (or sh -c or ssh or anything that takes shell code as an argument) embedding a variable expanded by the outer shell is as flawed as embedding {} expanded by find or xargs, especially when the variable comes from an untrusted source.

For example if the value of your $i was ; rm -f /important/file; and you let the outer shell expand it and embed the result in the string that is about to be interpreted as shell code, then bash -c spawned by screen would interpret code containing echo screen-; rm -f /important/file; and it would try to remove the /important/file.

With bash -c (or sh -c) the right general way is like this:

i=whatever
bash -c '
   for j in 1 2; do
      echo "screen-${1}-iter-${j}"
   done
' my-bash "$i"

(where my-bash is an arbitrary string). Now the expanded value of $i is passed to bash as an extra argument. Inside the shell code you retrieve it with ${1} (or $1). The inner shell will not interpret the value as shell code.

Always prefer static shell code; pass variables as arguments or in the environment.

Also note I properly double-quoted $i for the outer shell and screen-${1}-iter-${j} for the inner shell. In your first snippet the outer shell sees unquoted screen-${i} and the inner shell sees unquoted screen-${i}-iter-${j}. You get away with this because 1 and 2 are safe values (at least with the default $IFS). Still, IMO it's easier and less error-prone to always quote right by habit than to think each time if possible values are safe.