12

I want to check for the existence of multiple directories, say, dir1, dir2 and dir3, in the working directory.

I have the following

if [ -d "$PWD/dir1" ] && [ -d "$PWD/dir2" ] && [ -d "$PWD/dir3" ]; then
    echo True
else
    echo False
fi

But I suspect there is a more elegant way of doing this. Do not assume that there is a pattern in the names of the directories.

The goal is to check for the existence of a few directories and for the nonexistence of others.

I'm using Bash, but portable code is preferred.

Elegance
  • 123
  • 1
    “The goal is to check for the existence of a few directories and for the nonexistence of others.” Well, your example checks for existence of all listed directories. Can you elaborate on this part ? Is it all listed or any listed ? Do you need a check that passed values are in fact directories and not other types of files ? – Sergiy Kolodyazhnyy Mar 02 '19 at 05:28
  • 1
    You don't need the $PWD, by the way. [ -d "$PWD/dir1"] is equivalent to [ -d "dir1" ]. – terdon Mar 02 '19 at 14:43

6 Answers6

12

I would loop:

result=True
for dir in \
        "$PWD/dir1" \
        "$PWD/dir2" \
        "$PWD/dir3" 
do
    if ! [ -d "$dir" ]; then
        result=False
        break
    fi
done
echo "$result"

The break causes the loop to short-circuit, just like your chain of &&

glenn jackman
  • 85,964
7

If you already expect them to be directories and are just checking whether they all exist, you could use the exit code from the ls utility to determine whether one or more "errors occurred":

ls "$PWD/dir1" "$PWD/dir2" "$PWD/dir3" >/dev/null 2>&1 && echo All there

I redirect the output and stderr to /dev/null in order to make it disappear, since we only care about the exit code from ls, not its output. Anything that's written to /dev/null disappears — it is not written to your terminal.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
  • Can you help me understand this command? I know what file descriptors are. I know 1 is stdout, 2 is stderr and I know what redirecting is. I don't understand the significance of /dev/null, and I do not know how to parse the command. – Elegance Mar 01 '19 at 18:46
  • @Elegance I added a little explanation. For more in-depth answers regarding /dev/null, see https://unix.stackexchange.com/questions/163352/what-does-dev-null-21-mean-in-this-article-of-crontab-basics and https://unix.stackexchange.com/questions/438130/why-is-dev-null-a-file-why-isnt-its-function-implemented-as-a-simple-program – Jeff Schaller Mar 01 '19 at 18:51
  • Still trying to figure out how the syntax works. I read that &>filename redirects both stdout and stderr to filename. So couldn't the command be simplified (at least to me it is more simple) as ls "$PWD/dir1" "$PWD/dir2" "$PWD/dir2" &>/dev/null && echo All there? – Elegance Mar 01 '19 at 19:02
  • It could, but not portably -- plain sh does not understand &>; it would misinterpret that as "run me in the background and send stdout to the redirection". I spelled it out from habit and kept it there because of the "portable code" preference. – Jeff Schaller Mar 01 '19 at 19:15
  • Thank you very much, by the way. There's just one thing escaping me. ls "$PWD/dir1" "$PWD/dir2" "$PWD/dir2" >/dev/null This redirects the output to "nothing", right? Then, in my eyes, 2>&1 would take the the stderr (because of 2) of nothing and redirect to stdout (because of &1) of god knows what. I understand what this is doing, I just don't get the syntax, it's not consistent with the regular use of >. I know I'm wrong in saying this, I'm just trying to explain what's on my mind. Should I ask this in another question? One last question: can it be adapted for nonexistence? – Elegance Mar 01 '19 at 19:22
  • I redirected stdout for directories that exist and stderr for directories that don't exist -- we don't care about the contents of the former nor about ls's complaints about the latter. The redirections are processed in order, so after "dumping" stdout, stderr is then pointed to where stdout now points (the same /dev/null). See also: https://unix.stackexchange.com/questions/159513/what-are-the-shells-control-and-redirection-operators – Jeff Schaller Mar 01 '19 at 19:24
  • 4
    (1) You should probably use the -d option (a) so ls needs only to stat the directories, and not read them, and (b) so the command will succeed even if the user doesn’t have read access to the directories.  (2) I don’t see any reason to use "$PWD/" except to guard against directories whose names begin with - (and, of course, there are better ways to do that). – G-Man Says 'Reinstate Monica' Mar 02 '19 at 03:40
  • 2
    this command could take much longer to run than the test in the original question. also it doesn't check for directories. – Jasen Mar 02 '19 at 06:16
6

A loop might be more elegant:

arr=("$PWD/dir1" "$PWD/dir2" "$PWD/dir2")
for d in "${arr[@]}"; do
    if [ -d "$d"]; then
        echo True
    else
        echo False
    fi
done

This is Bash. A more portable one is Sh. There you can use the positional array:

set -- "$PWD/dir1" "$PWD/dir2" "$PWD/dir2"

Then to loop over it use "$@".

5

Why not just:

if [ -d "dir1" -a -d "dir2" -a -d "dir3" ]; then
    echo True
else
    echo False
fi
  • This is essentially what the OP started with, But I suspect there is a more elegant way of doing this – Jeff Schaller Mar 01 '19 at 20:52
  • 6
    Also POSIX discourages the use of -a: "-a and -o binary primaries (...) operators have been marked obsolescent": http://pubs.opengroup.org/onlinepubs/9699919799/ – Elegance Mar 01 '19 at 20:55
  • @JeffSchaller It's more terse since it does it all in one call to test. – David Conrad Mar 02 '19 at 00:27
  • @Elegance They're still supported on all the systems I use, and probably will be a hundred years from now. – David Conrad Mar 02 '19 at 00:27
  • 2
    it's not at all the same, the original invokes test (aka [) three times this invokes it once, – Jasen Mar 02 '19 at 06:17
  • 1
    https://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html#tag_20_128_16 is a more precise reference to the location of the POSIX statement. – G-Man Says 'Reinstate Monica' Mar 02 '19 at 15:45
  • In view of @G-ManSays'ReinstateMonica' 's comment then it looks like [-d "dir1" && -d "dir2" && -d "dir3"] would be compliant without using the obsolescent -a but instead shellcheck tells me to split them as in the OP's original version. – pbhj Mar 28 '21 at 00:50
  • @pbhj:  Obviously you didn't try that.   David Conrad's answer at least works; yours is wrong in a couple of ways. – G-Man Says 'Reinstate Monica' Mar 28 '21 at 04:16
  • I did try it (BASH, on Kubuntu) in the context of a different script (for my mopp-unofficial project on gitlab), but rewrote it by hand to match the question here. Like I said, shellcheck said splitting it was preferable - but not why, I didn't investigate - so I reverted to the split version which basically matches the original question. As it was a comment I didn't check my syntax, that wasn't the point of the comment -- please explain my error. – pbhj Mar 29 '21 at 19:32
  • fwiw, https://gist.github.com/pbhj/cd6bd6b83a1ccbaaf55b7f870e16b9d4 shows the shellcheck outputs in case anyone wants to follow up. – pbhj Apr 01 '21 at 19:22
4

As per the question, two portable shell functions that test for the existence and nonexistence of multiple directories:

# Returns success if all given arguments exists and are directories.
ck_dir_exists () {
    for dir do
        [ -d "$dir" ] || return 1
    done
}

# Returns success if none of the given arguments are existing directories.
ck_dir_notexists () {
    for dir do
        [ ! -d "$dir" ] || return 1
    done
}

Example:

$ mkdir dir1 dir2
$ ck_dir_exists dir1 dir2; echo $?
0
$ ck_dir_exists dir1 dir2 dir3; echo $?
1
$ ck_dir_notexists dir1 dir2 dir3; echo $?
1
$ ck_dir_notexists dir3 dir4; echo $?
0
Kusalananda
  • 333,661
1

The goal is to check for the existence of a few directories and for the nonexistence of others.  [Emphasis added]

Building on glenn jackman’s answer, we can test for the nonexistence of other names like this:

result=True
for dir in \
        "$PWD/dir1" \
        "$PWD/dir2" \
        "$PWD/dir3" 
do
    if ! [ -d "$dir" ]; then
        result=False
        break
    fi
done
for dir in \
        "$PWD/dir4" \
        "$PWD/dir5" \
        "$PWD/dir6" 
do
    if [ -e "$dir" ]; then                      # Note: no “!”
        result=False
        break
    fi
done
echo "$result"
I used [ -e "$dir" ] to test whether "$dir" exists; i.e., if dir5 exists but is a file, the result is False.  If you want only to test whether the names in the second group are directories, use [ -d "$dir" ], like in the first loop.

Since we’re talking about checking for the existence of things in the current working directory, it’s probably not necessary to specify $PWD/ on the names; just do

for dir in \
        "dir1" \
        "dir2" \
        "dir3" 
do
      ︙