# The menu we are making looks like this
[me@parentfolder]$ directoryPicker $pwd
1) Subfolder_1
2) Subfolder_2
3) Subfolder_3
#?
To make a generic menu, call select
with an array, and it displays the choices. You can get complicated and have DIRECTORY
# The select function portion of the example
select dir in "${dirs[@]}"; do
# This will be run only on the user's choice, the function itself
# handles displaying the array values as a menu
DIRECTORY="${PARENT}/${dir}"
break;
done
You can declare an array with values for each item under a directory by using a subshell to call ls
.
# make an array using a directory listing
declare -a dirs=($(ls -d *))
# Want to check what is in here? Echo all the entries with [*]
echo ${dirs[*]}
But you probably want to do a little filtering to remove files, since passing them to your calling function won’t do much and it makes the menu less readable.
The basics of looping through a bash array probably look familiar, but for the iteration note that
${array[@]}
will give you each array value in its own variable, and ${array[*]}
will return them all as a single variable.# a basic array loop
for dir in "${dirs[@]}"; do
echo ${dir}
done
To remove an item from an array, you want to unset
that index, so you’re going to give this loop index
let
index=0
for dir in "${dirs[@]}"; do
if [ ! -d "${PARENT}/${dir}" ]; then
# unset an array element by referencing the variable _name_,
# not using the variable. But the key will be the variable _value_
# so you still have a $ in there.
# e.g. array[1]
unset 'dirs[$index]'
fi
# Incrementing a variable either requires arithmetic expansion with
# double parenthesis, or using let
let "index++"
done
All put together, you get this handy function that takes a parent directory and returns a menu to choose only subdirectories.
# Fetch a list of directories under the passed one
# @param[1] parent directory
# @returns DIRECTORY, directory path
function directoryPicker {
# $1 = Parent Directory
PARENT=$1
cd $PARENT
# make an array using a directory listing
declare -a dirs=($(ls -d *))
# We want to display only folders as options,
# so remove non-directories
index=0
for dir in "${dirs[@]}"; do
if [ ! -d "${PARENT}/${dir}" ]; then
# unset an array element by referencing the variable name,
# not using the variable. But the key will be the variable value
# e.g. array[1]
unset 'dirs[$index]'
fi
# Incrementing a variable either requires arithmetic expansion with
# double parenthesis, or using let
let "index++"
done
# Then display the array as a numbered choice list
select dir in "${dirs[@]}"; do
DIRECTORY="${PARENT}/${dir}"
break;
done
}
To put this select menu into use, you can call this function from inside another, passing it a parent directory. This example will fetch all the updates for all the git repos in a subfolder.
I have my repos checked out into groups based on the organization or user that owns them, or based on a theme they have in common. So there are folders for my employer, my personal stuff, friends, and themed project topics like “WordPress” or “cryptocurrency”. So passing my updateRepos
function a menu lets me update only a specific set of repos at once.
# Update all the repos under a parent folder
function updateRepos {
# Get a list of subfolders, using our chooser to show a list
directoryPicker "/Users/$(whoami)/Developer/github.com"
REPOBASE=$DIRECTORY
# Like we did in the chooser, create an array of subfolders
cd $REPOBASE
declare -a repos=($(ls -d *))
# for each repo, fetch/pull latest
for repo in "${repos[@]}"; do
# Only bother with directories that have a .git subfolder
if [[ -d $REPOBASE/${repo} && -d $REPOBASE/${repo}/.git ]]; then
echo "Checking ${repo} for updates"
cd $REPOBASE/${repo}
git fetch
git pull
fi
done
}