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.