I'm trying to create a simple Shell script which involves selecting a random directory from the current working directory, and navigating to it.
Can anyone illustrate how to list all directories, and from this list, randomly select one?
I'm trying to avoid listing all directories to a text file and simply selecting a random line from that file (which is simple enough).
My initial attempts included using the ls -d */
command to list only directories. This command worked when it was entered into the terminal, however returned the error:
ls: */: No such file or directory
when I tried to implement it into this script:
DIR_LIST=` ls -d */`
echo "$DIR_LIST"
Try this:
ls -d */ | shuf -n 1
shuf selects random directory from your output.
Your script will look like this:
DIR_LIST=`ls -d */`
echo "$DIR_LIST" | shuf -n 1
find . -maxdepth 1 -type d ! -path . | shuf -n 1
No shuf
version:
# To exclude hidden directories, use -name like so:
# find ... -name ".*"
# A version of find using only POSIX options:
# This might be slower for large directories (untested). Will need to be modified to list anything other than current directory.
# dirs="$(find . -type d ! -path "./*/*" ! -name ".")"
# Includes hidden directories.
dirs="$(find . -maxdepth 1 -type d ! -path .)"
numlines="$(printf "%s\n" "${dirs}" | wc -l)"
lineno="$((${RANDOM} % ${numlines} + 1))"
random_line="$(printf "%s\n" "${dirs}" | sed -n "${lineno}{p;q}")"
echo "Your selected directory: ${random_line}"
Edit: Improved code based on comments.
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:
shuf
is NOT preinstalled.
gshuf
) via Homebrew with brew install coreutils
.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.
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.jot
, which simply picks a single integer in the range.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With