bash中路径名称的模式匹配


10

我想对目录中的子目录列表进行操作。考虑:

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

这给

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

但我想值对应于路径的第二部分 elfgl等我知道如何去掉领先x86-headers

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

这使

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

我也知道如何删除路径中的后续术语。即下降一级:

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

elf
gl
gmp
gnome2
gtk2
jni
libc

但是,尝试将这些结合起来是行不通的。即

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

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

毫无疑问,这是不正确的语法,但是我不知道正确的语法。当然,可能会有更好的方法来执行此操作。如果我使用的是Python,我会使用split on /将每个路径分成一个列表,然后选择第二个元素,但是我不知道如何在bash中做到这一点。

编辑:感谢您的答案。我还应该问,是否可以轻便地执行此操作,如果可以,如何执行?

Answers:


9

您不能将它们与bash(或POSIXly)结合使用,必须分两步完成。

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

可以移植到任何POSIX shell。如果您需要可移植到Bourne shell(但是为什么还要标记问题/ bash?),以防您要移植到Solaris并被迫使用那里/bin/sh而不是标准的标准sh,或者移植到使用了20年的旧系统) ,您可以使用这种方法(与POSIX shell一起使用):

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

(以上是在极少数情况下,将变量不加引号有意义的情况之一)。

仅作记录,使用zsh:

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

(该的的AIL ħ所述的EAD ħ EAD的文件)。

或为您的python方法:

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

中的分号i=${x#*/}; i=${i%%/*}是可选的,对吧?
Dimitre Radoulov

3
@DimitreRadoulov这是可选的,但是某些外壳(如某些旧版本的ash/)dash会从右到左运行分配(这是Bourne外壳的行为方式),并在这种情况下引起问题。
2013年

7

参数扩展不允许您嵌套表达式,因此您必须分两步进行操作,并赋值:

i=${x##x86-headers/}
i=${i%%/*}

您确实可以选择在此处使用数组,但我认为比上述PE更为麻烦:

IFS=/
set -f # Disable globbing in case unquoted $x has globs in it
i=( $x )
set +f
echo "${i[1]}"

然后是使用外部命令的第三个选项。对于简短的文件列表,这通常是效率最低的选项,但是随着文件数量的增加,它的扩展性最佳:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3

但是,并非所有的shell都zsh允许嵌套。
斯特凡Chazelas

3
@StephaneChazelas足够公平,但是这个问题被标记了bash
kojiro

2
regex="x86-headers/([^/]*)/.*"
for f in x86-headers/*/C/populate.sh; do [[ $f =~ $regex ]] && echo "${BASH_REMATCH[1]}"; done
elf
gl
gmp
gnome2
gtk2
jni
libc

2

使用波纹管时要非常小心:

# What happen if no files match ?
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

因为您最终可能会做一个echo *。在这种情况下,这只是好笑,但是如果您是root,您CWD是is /并且您想要删除某些子目录/tmp而不是回显它们,那可能会更糟!

要解决此问题,您可以执行以下操作:

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

请注意shopt -u nullglob最后的,以在循环后恢复默认的bash行为。

一个更便携式的解决方案可能是:

for x in x86-headers/*/C/populate.sh; do
  [ -e $x ] || break
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

##%%参数替换在ksh88中有效)。

单击此链接可以更完整地讨论nullglob

请注意,如果您的中间目录名称中带有空格,则上述3个解决方案将失败。要解决此问题,请在变量替换中使用双引号:

for x in x86-headers/*/C/populate.sh; do
  [ -e "$x" ] || break
  x="${x##x86-headers/}"
  x="${x%%/*}"
  echo "$x"
done

0

要在路径名上进行模式匹配,可以将IFSshell变量设置为/,然后使用shell参数扩展。

在子Shell中执行所有这些操作有助于保持父Shell的环境不变!

# cf. "The real Bourne shell problem",
# http://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellLists

paths='
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
'

IFS='
'

# version 1
for x in $paths; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done


# version 2
set -- ${paths}
for x in $@; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.