41

I want to write a shell script to show a list of directories entered by a user and then for a user to select one of the directories with an index number based on how many directories there are

I'm thinking this is some kind of array operation, but im not sure how to do this in shell script

example:

> whichdir
There are 3 dirs in the current path
1 dir1
2 dir2
3 dir3
which dir do you want? 
> 3
you selected dir3!
qodeninja
  • 10,946
  • 30
  • 98
  • 152
  • 1
    You really want the `select` builtin command for this, it's what it was made for. Dennis has a good example of it. – SiegeX Dec 21 '10 at 23:04

4 Answers4

54
$ ls -a
./ ../ .foo/ bar/ baz qux*
$ shopt -s dotglob
$ shopt -s nullglob
$ array=(*/)
$ for dir in "${array[@]}"; do echo "$dir"; done
.foo/
bar/
$ for dir in */; do echo "$dir"; done
.foo/
bar/
$ PS3="which dir do you want? "
$ echo "There are ${#array[@]} dirs in the current path"; \
select dir in "${array[@]}"; do echo "you selected ${dir}"'!'; break; done
There are 2 dirs in the current path
1) .foo/
2) bar/
which dir do you want? 2
you selected bar/!
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
30

Array syntax

Assuming you have the directories stored in an array:

dirs=(dir1 dir2 dir3)

You can get the length of the array thusly:

echo "There are ${#dirs[@]} dirs in the current path"

You can loop through it like so:

let i=1

for dir in "${dirs[@]}"; do
    echo "$((i++)) $dir"
done

And assuming you've gotten the user's answer, you can index it as follows. Remember that arrays are 0-based so the 3rd entry is index 2.

answer=2

echo "you selected ${dirs[$answer]}!"

Find

How do you get the file names into an array, anyways? It's a bit tricky. If you have find that might be the best way:

readarray -t dirs < <(find . -maxdepth 1 -type d -printf '%P\n')

The -maxdepth 1 stops find from looking through subdirectories, -type d tells it to find directories and skip files, and -printf '%P\n' tells it to print the directory names without the leading ./ it normally likes to print.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • 1
    @johnstamos, spaces aren't the only problem -- if you have a directory created with `mkdir '*'`, you also need to worry about it being expanded, and every *other* directory at that location being listed twice, unless one has run `set -f` to disable globbing. – Charles Duffy Jun 21 '18 at 21:11
  • Use `-mindepth 1 -maxdepth 1` to *not* print a empty line (which is the `.` directory) – debuglevel Mar 20 '20 at 23:26
12
#! /bin/bash

declare -a dirs
i=1
for d in */
do
    dirs[i++]="${d%/}"
done
echo "There are ${#dirs[@]} dirs in the current path"
for((i=1;i<=${#dirs[@]};i++))
do
    echo $i "${dirs[i]}"
done
echo "which dir do you want?"
echo -n "> "
read i
echo "you selected ${dirs[$i]}"
Jester
  • 56,577
  • 4
  • 81
  • 125
0

Update: my answer is wrong

Leaving it here to address a common misunderstanding, below the line is erroneous.


To put the directories in an array you can do...

array=( $( ls -1p | grep / | sed 's/^\(.*\)/"\1"/') )

This will capture the dir names, including those with spaces.


Extracting from comments:

literal quotes don't have any effect on string-splitting, so array=( echo '"hello world" "goodbye world"' ) is an array with four elements, not two

@Charles Duffy

Charles also supplied the following link Bash FAQ #50 which is an extended discussion on this issue.

I should also draw attention to the link posted by @Dennis Williamson - why I shouldn't have used ls

ocodo
  • 29,401
  • 18
  • 105
  • 117
  • 1
    This will fail if any directories have a space in their names. – Dennis Williamson Dec 21 '10 at 00:57
  • No, that's not where the problem is. It's because you're using `ls`. See [some reasons not to](http://mywiki.wooledge.org/ParsingLs). – Dennis Williamson Dec 22 '10 at 01:00
  • The update works though, granted `for d in */ do` is better. Linked article noted, I've never had problems with `ls` garbling names, but it's worth knowing that bad filenames will be handled poorly, thanks. – ocodo Dec 22 '10 at 03:02
  • @ocodo, no, the update does not work; literal quotes don't have any effect on string-splitting, so `array=( echo '"hello world" "goodbye world"' )` is an array with four elements, not two -- the first of those elements being `"hello`, and the second being `world"`. See [BashFAQ #50](http://mywiki.wooledge.org/BashFAQ/050) for a discussion of other errors caused by the same misunderstanding. – Charles Duffy Jun 21 '18 at 21:05
  • @CharlesDuffy thank you for the link and clarification. I'll leave this answer here but annotate it as wrong, for any future readers. – ocodo Jun 23 '18 at 03:46