Bash – Pattern matching on path names in bash

bashpatternsstringwildcards

I want to act on a list of subdirectories in a directory. Consider:

for x in x86-headers/*/C/populate.sh; do echo $x; done

This gives

x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh

But I want values that corresponds to the second part of the path,
elf, gl, etc. I know how to strip off the leading x86-headers.

for x in x86-headers/*/C/populate.sh; do i=${x##x86-headers/}; echo $i; done

which gives

elf/C/populate.sh
gl/C/populate.sh
gmp/C/populate.sh
gnome2/C/populate.sh
gtk2/C/populate.sh
jni/C/populate.sh
libc/C/populate.sh

I also know how to strip off later terms in the path. I.e. going down one level:

cd x86-headers
for x in */C/populate.sh; do i=${x%%/*}; echo $i; done

gives

elf
gl
gmp
gnome2
gtk2
jni
libc

But, trying to combine these doesn't work. I.e.

for x in x86-headers/*/C/populate.sh; do i=${${x##x86-headers}%%/*}; echo $i; done

gives

bash: ${${x##x86-headers}%%/*}: bad substitution

No doubt this is incorrect syntax, but I don't know the correct syntax.
Of course there might be better ways to do this. If I was using Python, I'd use split on /to break each path into a list, and then pick the second element, but I don't know how to do that in bash.

EDIT: Thanks for the answers. I should also have asked, is it possible to do this portably, and if so, how?

Best Answer

You can't combine them with bash (or POSIXly), you have to do it in two steps.

i=${x#*/}; i=${i%%/*}

That's portable to any POSIX shell. If you need portability to the Bourne shell (but why would you tag your question /bash then?) as well in case you're porting to Solaris and are forced to use /bin/sh instead of the standard sh there, or porting to 20 year old systems), you could use this approach (which will work as well with POSIX shells):

IFS=/; set -f
set x $x
i=$3

(above is one of the very rare cases where it makes sense to leave a variable unquoted).

Just for the record, with zsh:

print -rl -- x86-headers/*/C/populate.sh(:h:h:t)

(the tail of the head of the head of the file).

Or for your python approach:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}
Related Question