3
\$\begingroup\$

I'd like to create directories on a remote VM, but only if they do not exist. What's more, if creating the directory results in an error (exit status != 0) I'd like the script to exit as well, or at least print an error. Is there a more succinct way to do this than what I have below?

UPDATE: Just to clarify, I want to test -d first so mkdir -p won't produce output about the directory already existing.

#!/bin/bash

mydirs=(/var/www/files /var/www/photos /var/www/info)
for d in ${mydirs[@]}; do
   ssh remotehost "test -d $d"
   res=$?
   test $res -ne 0 && { ssh remotehost "mkdir -p $d"; res=$?; }
   test $res -ne 0 && { echo "error during mkdir on remote"; exit 1; }
done
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Normally you'd put the script on the server / VM itself, and run it after logging in through SSH. It would certainly be more efficient than running separate commands over SSH. \$\endgroup\$
    – esote
    Commented Feb 21, 2019 at 1:55
  • \$\begingroup\$ Thanks.This is part of a larger provisioning system which configures a VM into a specialized server. What I'm really wondering about is making it work with less code. Maybe what I have is about as trim as it gets. I can do away with the $res variables but as far as I see, that's about it. Just thought I'd try this forum before comitting the code in. \$\endgroup\$ Commented Feb 21, 2019 at 2:16
  • \$\begingroup\$ @ServerFault If you are designing a system for provisioning multiple VMs, consider using proper system administration tools such as Puppet or Ansible instead of rolling your own scripts. \$\endgroup\$ Commented Feb 21, 2019 at 6:41
  • \$\begingroup\$ @200_success Good reminder, but please stay on-topic. This question is about code review, not process review. \$\endgroup\$ Commented Feb 21, 2019 at 14:21

2 Answers 2

4
\$\begingroup\$

Running ssh in a loop is not efficient. Since the script is not interactive, you could pass the entire script to a remote Bash process on stdin, so that loop will run entirely on the remote server, locally:

ssh remotehost bash << "EOF"
mydirs=(/var/www/files /var/www/photos /var/www/info)
for d in ${mydirs[@]}; do
   test -d $d
   res=$?
   test $res -ne 0 && { mkdir -p $d; res=$?; }
   test $res -ne 0 && { echo "error during mkdir on remote"; exit 1; }
done
EOF

Notice the double-quotes around the here document label, this is to avoid variable interpolation. The entire script is passed to the remote shell literally.

I made only the minimal changes to illustrate the point. Some important improvements are well advised:

  • All variables used as command line arguments should be double-quoted: "${mydirs[@]}", "$d", and so on.
  • As a comment mentioned, when using mkdir -p, it's unnecessary to test if the directory exists.
  • Now that the main operations run in a single process, the pipeline can be simplified.
  • As another comment mentioned, consider investing in learning a proper system administration tool such as Puppet, Ansible, Chef, or similar.
\$\endgroup\$
1
  • \$\begingroup\$ Thanks, this is awesome. I never thought about using ssh with a HERE doc \$\endgroup\$ Commented Feb 21, 2019 at 14:18
3
\$\begingroup\$

Succinct:

#!/bin/bash -e
ssh remotehost mkdir -p /var/www/{files,photos,info}

Failure to create any directory will give non-zero exit for ssh, which causes the outer script to exit with error, because of the -e switch.

mkdir will print suitable error messages if it encounters errors. If you prefer the error message to appear on standard output (like echo would do), you can redirect standard error (aka "filehandle 2") to stdout (filehandle 1) by appending 2>&1:

#!/bin/bash -e
ssh remotehost mkdir -p /var/www/{files,photos,info} 2>&1
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Trading some succinctness for a little extra efficiency, we could prefix the command with exec since there's no need to return to the shell. \$\endgroup\$ Commented Feb 21, 2019 at 9:21
  • \$\begingroup\$ There's a slight difference IIUC: the original stops at the first error, but mkdir will continue with subsequent arguments before exiting with a failure code. \$\endgroup\$ Commented Feb 21, 2019 at 10:26
  • \$\begingroup\$ @TobySpeight good point and that makes -e unnecessary so it's only 2 extra characters. \$\endgroup\$ Commented Feb 21, 2019 at 10:29
  • \$\begingroup\$ This is a great way to shorten it up but how do I get the test -d in there so no output (File exists) is produced if mkdir -p fails? \$\endgroup\$ Commented Feb 21, 2019 at 14:24
  • \$\begingroup\$ mkdir -p does not print "file exists" if the file exists. It does nothing. \$\endgroup\$ Commented Feb 21, 2019 at 17:58

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.