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
2 Answers 2
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.
-
\$\begingroup\$ Thanks, this is awesome. I never thought about using ssh with a HERE doc \$\endgroup\$Server Fault– Server Fault2019年02月21日 14:18:17 +00:00Commented Feb 21, 2019 at 14:18
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
-
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\$Toby Speight– Toby Speight2019年02月21日 09:21:46 +00:00Commented 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\$Toby Speight– Toby Speight2019年02月21日 10:26:44 +00:00Commented Feb 21, 2019 at 10:26 -
\$\begingroup\$ @TobySpeight good point and that makes
-e
unnecessary so it's only 2 extra characters. \$\endgroup\$Oh My Goodness– Oh My Goodness2019年02月21日 10:29:35 +00:00Commented 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 ifmkdir -p
fails? \$\endgroup\$Server Fault– Server Fault2019年02月21日 14:24:44 +00:00Commented Feb 21, 2019 at 14:24 -
\$\begingroup\$
mkdir -p
does not print "file exists" if the file exists. It does nothing. \$\endgroup\$Oh My Goodness– Oh My Goodness2019年02月21日 17:58:34 +00:00Commented Feb 21, 2019 at 17:58
$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\$