The error message you're seeing indicates that the current directory happens to have no (non-hidden) subdirectories - the script's working directory is probably different from what you expect - cd to the desired dir. first.
Aside from that, however, it's better not to parse the output of ls;
using pathname expansion (globbing) directly is both simpler and more robust - see below.
Note: Originally, this answer contained only one solution, based on a bash array, which has been replaced with multi-line-string solutions, given the generic title of the question.
The question is tagged osx, which has two implications:
- GNU utility
shuf is NOT preinstalled.
- However, you can install it (as
gshuf) via Homebrew with brew install coreutils.
- BSD utility
jot IS preinstalled; while far from being the equivalent of shuf, it is capable of choosing a random number from a range of integers.
A jot-based solution:
dirs=$(printf '%s\n' */) # collect subdir. names, each on its own line
randomLineNum=$(jot -r 1 1 $(wc -l <<<"$dirs")) # pick random line number
randomDir=$(sed -n "$randomLineNum{p;q;}" <<<"$dirs") # extract random dir. name
cd "$randomDir" # use the randomly selected dir.
printf '%s\n' */ prints all directory names (with a terminating /); each on its own line.
- Note that there is no good reason to use
find in a simple case like this; the glob */ is sufficient to match all subdirectories.
jot -r 1 1 $(wc -l <<<"$dirs") returns a randomly chosen integer between 1 and the number of lines in $dirs (wc -l <<<"$dirs"), i.e., the number of subdirs.
sed -n '<lineNumber>{p;q;}' is a sed idiom that prints (p) only the line with the specified number and then quits (q) processing the file.
A POSIX-compliant solution:
Note: This can be handy if you cannot assume the presence of jot,shuf, or even bash.
dirs=$(printf '%s\n' */)
randomLineNum=$(awk -v count="$(printf '%s\n' "$dirs" | wc -l)" \
'BEGIN { srand(); print 1 + int(rand()* count) }')
randomDir=$(printf '%s\n' "$dirs" | sed -n "$randomLineNum{p;q;}")
cd "$randomDir"
printf '%s\n' "$dirs" | wc -l counts the number of lines in $dir
awk -v count=<lineCount> 'BEGIN { srand(); print 1 + int(rand()* count) }' uses awk to print a random number between 1 and :
srand() seeds the random generator, and rand() returns a random float >= 0 and < 1; by multiplying with the line count, converting to an integer and adding 1, a random number >= 1 <= line count is returned.
For the sake of completeness, let's look at shuf solutions:
Simplest, but inefficient solution using shuf:
printf '%s\n' */ | shuf -n 1
shuf -n 1 shuffles all input lines and then prints only the first of the shuffled lines.
This is inefficient, because even though only 1 random line is needed, shuf invariably reads all input lines at once into memory, and shuffles all of them instead of just picking 1 random one; with a small number of lines that probably won't matter, however.
Slightly more cumbersome, but more efficient shuf solution:
Note that this solution is similar to the jot-based one above.
dirs=$(printf '%s\n' */)
randomLineNum=$(shuf -n 1 -i 1-"$(wc -l <<<"$dirs")")
randomDir=$(sed -n "$randomLineNum{p;q;}" <<<"$dirs")
cd "$randomDir"
shuf -n 1 -i 1-"$(wc -l <<<"$dirs")" shuffles integers in the range between 1 and the count of lines in $dirs (wc -l <<<"$dirs"), and prints only one (the first) -n 1 of the shuffled numbers, effectively yielding a single, random line number.
- Shuffling only the range of line numbers rather than the lines themselves will typically be more efficient, but note that a random permutation of all integers in the range is still built up in memory - unlike with
jot, which simply picks a single integer in the range.