0

I have a for loop that counts and makes changes on servers. Each server has numerous users and I need to perform some simple operations on each user's account. Regardless of what needs to be done, I need to count up for the IP space of each server and for each run of the loop, I need a different username to be used. users.txt contains

usera
userb
userc

My loop looks like

for (( counter=1; counter<99; counter++))
   do
   if [ $counter -lt 10 ];
   then
     ssh -o StrictHostKeyChecking=no user@server0${counter}.domain.com  mkdir /home/${user}/newdir
   else
     ssh -o StrictHostKeyChecking=no user@server${counter}.domain.com  mkdir /home/${user}/newdir
   fi
done

But none of this addresses the $user for each line and I'm not sure of how to best approach it. Ideally the script would use:

ssh -o StrictHostKeyChecking=no [email protected]  mkdir /home/username/newdir

Thank you for any help or guidance you can offer.

3
  • also, what do you mean by ssh ... ; mkdir /home/username/newdir? Did you mean to run the mkdir command on the remote server? Because the ; terminates the ssh command and the mkdir runs on your machine. Commented Mar 24, 2020 at 21:34
  • True, I've edited my question to reflect this, my mistake. Commented Mar 24, 2020 at 21:49
  • BashFAQ #1 is the usual reference on iterating over a file line-by-line in bash, though doing that in conjunction with ssh one needs to be wary of the issue discussed in BashFAQ #89. Commented Mar 24, 2020 at 23:02

2 Answers 2

1

A cautious answer might look like:

readarray -t users <users.txt
printf -v cmd_q '/home/%q/newdir ' "${users[@]}"

for (( counter=0; counter<99; counter++ )); do
  printf -v counter_padded '%02d' "$counter"
  ssh "user@server${counter_padded}.domain.com" 'bash -s' <<<"mkdir -p -- $cmd_q"
done

What noteworthy advantages (and disadvantages) does this have?

  • We require bash 4.0 on the local system, for the readarray feature. This lets us read users.txt only once, populating an array "${users[@]}", which we then use to generate a string "$cmd_q", which a shell-quoted version of our list of home directory names (more on that later!)
  • Using the %q format string is guaranteed to generate an eval-safe shell-quoted string (when the remote shell is provided by bash), so some joker saying their username is $(rm -rf ~) won't cause you a bad day.
  • There's no dependency on a nonstandard seq, command, and no letting ssh combine its arguments to generate a string to pass to the remote command -- instead, we pass it an explicit list of arguments, already correctly quoted.
  • We run mkdir only once, not a separate time per directory name. (xargs is capable of doing this kind of thing too, but it turns off that behavior when used with -I{}).
  • $(...anything...) is generally slow, and we avoid it here. That won't result in a noticeable performance difference in this particular case (since another approach wouldn't necessarily put the command substitution in the loop body at all, and even if it did, ssh is much slower by comparison), but it's a good habit to get into for when you have a use case where your loop could otherwise be fast.

If you needed this to run with an arbitrarily large list of usernames, I would change mkdir -p -- $cmd_q to printf '%s\0' $cmd_q | xargs -0 mkdir -p --, which can handle more filenames than can fit on a single mkdir command line. (Because printf is a shell builtin, not an external command, it isn't subject to the operating system's command-line length limits).

Sign up to request clarification or add additional context in comments.

Comments

0

You just need an inner loop. Here's one way which uses xargs on the remote server, passing the usernames through on stdin. (Also illustrated: using printf to generate the list of server names. But note that this requires that the server name not include whitespace or any other shell metacharacter)

for server in $(printf "user@server%02d.domain.com" $(seq 99)); do
  cat users.txt |
  ssh -o StrictHostKeyChecking=no $server \
      xargs -I{} mkdir "/home/{}/newdir"
done

That works because the command you're running on the remote server is a single shell command, but it's not totally obvious how to extend that to a more complicated command. It's also not the most efficient since you could have created the three (or however many) directories with a single mkdir.

If the script you're trying to run on the remote server is not too complicated, you can wrap it in sh -c or even bash -c (if bash is installed on the remote hosts and you want to take advantage of its features). This works for simple sequences of commands, but managing quotes properly when you have two levels of quotation going on can be a real headache.

4 Comments

Why -I{}? It'd be considerably more efficient (only running the minimum possible number of copies of mkdir) to, say, sed -e 's@^\(.*\)$@/home/\1/newdir@' | ssh "$server" "xargs -d \$'\\n' mkdir --" with no -I, so xargs is able to put as many directory names on each mkdir command as possible.
@CharlesDuffy: i was going to use printf actually, but then I thought it would probably be easier to work with the way I had it because OP indicated that it was just a simple example of what was desired.
@CharlesDuffy: and I did use %02d to avoid the if. So that doesn't set us apart :-) I even mentioned it in the narrative.
Oops, you're right! Pulled that out of my answer text.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.