(bash
variation further down)
In the zsh
shell, the last match of a globbing pattern can be had by adding the globbing qualifier ([-1])
to the end of the pattern. In this example, we look for files whose names end in .sh
:
$ print -rC1 ./**/*.sh
./local/sbin/scheduled-backup.sh
./local/sbin/update-cvs.sh
./local/sbin/update-system.sh
./local/sbin/zerofill.sh
$ print -rC1 ./**/*.sh(.D[-1])
./local/sbin/zerofill.sh
The added .
and D
in the qualifier makes sure that we only find regular files and that we also match hidden names (as with dotglob
set in bash
).
This could be used to change into the directory containing the found file:
$ cd ./**/*.sh(.D[-1]:h)
(here using the csh/vi-style :h
modifier to get the head (dirname) of the file)
As a shell function that takes the name of a file (not a pattern):
goto_file () cd ./**/$1(.D[-1]:h)
This would not rely on filenames being sane (not containing newlines etc.)
In bash
, assuming you first set the globstar
and dotglob
shell options using shopt -s globstar dotglob
, you could do a similar thing in two steps:
$ set -- ./**/*.sh
$ cd "$( dirname -- "${@: -1}" )"
This sets the positional parameters to the list of matching pathnames, then uses the last of these in a call to dirname
, and uses the result of that with cd
. With bash
, there's no guarantee that the expansion of the glob will be a regular file though.
You could obviously use a named array instead:
$ stuff=( ./**/*.sh )
$ cd "$( dirname -- "${stuff[-1]}" )"
As a shell function, taking the name of a file (not a pattern):
goto_file () {
local pathnames
pathnames=( ./**/"$1" )
cd "$( dirname -- "${pathnames[-1]}" )"
}
This requires that at least the globstar
option has been set in the calling shell. We can set it on demand though:
goto_file () {
local pathnames
if [[ $BASHOPTS != *globstar* ]]; then
shopt -s globstar
trap 'shopt -u globstar' RETURN # unset globstar on return
fi
pathnames=( ./**/"$1" )
cd "$( dirname -- "${pathnames[-1]}" )"
}
This, like the zsh
variation, would not rely on filenames being sane (not containing newlines etc.), with the exception that the directory name cannot end in newline characters since these would be stripped off when the dirname
command substitution is returning its value.