I apologize for the non satisfactory context. I did not solve how to use associative arrays as a lookup table and used an ordinary array instead.
Context, operating system, bash version
More than 20 years ago, at the release of bash 2.0, I redefined the cd function and I have used my own version of cd ever since on various solaris, bsd and linux systems. Typing cd without any arguments would list the top 7 directories of DIRSTACK and typing cd with a number argument would change directory using the DIRSTACK. It was slightly annoying that the top of DIRSTACK did not necessarily contain unique directories but my cd function was good enough for usage and I never got around to refining it.
The other day I noticed that bash has evolved and now has associative arrays which I thought would solve the issue with the potentially repetitive directory listing.
The problem
I do not know how to check if a key already exists in an associative array. The exotic ${arr[$i]+7} was something I tried reading
wiki.nix-pro.com/view/BASH_associative_arrays#Check_if_key_exists
The basic algorithm I want to do is
foreach directory in DIRSTACK
if the directory has not been presented to the user
present the directory to the user
remember (i.e. store) the directory
which in a sample test code would be
function dirlist () {
declare -A arr
for i in /usr /usr/local /usr "/foldername with spaces" "/foldername with spaces"
do
echo checking ${i}
if [[ ${arr[$i]} != 17 ]] ; then
echo " handling $i and adding it to arr"
arr[ $i ]=17
fi
done
}
The above program yields
checking /usr
handling /usr and adding it to arr
checking /usr/local
handling /usr/local and adding it to arr
checking /usr
handling /usr and adding it to arr
checking /foldername with spaces
handling /foldername with spaces and adding it to arr
checking /foldername with spaces
handling /foldername with spaces and adding it to arr
but what I expected (hoped) it to print was
checking /usr
handling /usr and adding it to arr
checking /usr/local
handling /usr/local and adding it to arr
checking /foldername with spaces
handling /foldername with spaces and adding it to arr
The workaround
I rewrote my old cd function using a simple array for lookup instead of an associated array. The performance hit is negligible but it is annoying that I could not get the associated array to work. It was not without pain to implement since I haven't done any bash programming in 20 years. My new cd function seems to work all right though I haven't tested it extensively. I provide it here for context.
unset -f cd
function cd () {
local -i maxnrDirs=7
local -i nrDirs=0 # index of dirs as shown to user
local visited=()
local -i founddir=0
# if argument is a valid directory , change directory
if [ -d "$1" ]; then
pushd "$1" > /dev/null;
else
for ((i=1; i < ${#DIRSTACK[@]}; i++)); do
# keep track of already listed directories,
# Note, should be done with associative array
# this was on my TODO list for 20 years
local -i found=0
for ((j=0; j < ${#visited[@]}; j++)); do
if [[ ${visited[$j]} == ${DIRSTACK[${i}]} ]]
then
found=17
break
fi
done
# handle directory option if not found
if [ $found == 0 ] ; then
((nrDirs+=1))
# if there is no argument to cd, list previous dirs
if [ -z "$1" ]; then
founddir=17
echo " $nrDirs ${DIRSTACK[$i]}";
# if there is a number argument, check against DIRSTACK
else
if [ "$1" == ${nrDirs} ]; then
if [ "${DIRSTACK[${i}]}" ]; then
if [ -d "`dirs -l +${i}`" ]; then
pushd "`dirs -l +${i}`" > /dev/null ;
founddir=${i};
fi;
fi;
fi;
fi
local dir=${DIRSTACK[$i]} # for quoting directory with spaces
visited+=("$dir") # visited+=("${DIRSTACK[$i]}") does not work
if [ $nrDirs == $maxnrDirs ]; then
break;
fi
fi
done;
if [ ${founddir} == 0 ]; then
# Handle special characters, invent new ones ....?
if [ "$1" == - ]; then
pushd "`dirs -l +1`" > /dev/null;
elif [ "$1" == "..." ]; then
if [ -d "../../" ]; then
pushd "../../../" > /dev/null ;
fi
elif [ "$1" == "...." ]; then
if [ -d "../../../" ]; then
pushd "../../../" > /dev/null ;
fi
elif [ ! -z "$1" ]; then
# error message, TODO check \ ??
echo ${1} not a valid directory;
fi;
fi;
fi
}
complete -A directory cd
complete -A directory pushd
and here is a sample how it works
/> export PS1='\w> '
/> cd /
/> dirs -c
/> cd /usr/
/usr> cd local/
/usr/local> cd /etc/
/etc> cd cron.d/
/etc/cron.d> cd
1 /etc
2 /usr/local
3 /usr
4 /
/etc/cron.d> cd 2
/usr/local> cd -
/etc/cron.d> cd
1 /usr/local
2 /etc/cron.d
3 /etc
4 /usr
5 /
/etc/cron.d> dirs -p
/etc/cron.d
/usr/local
/etc/cron.d
/etc
/usr/local
/usr
/