bash:为路径名提取最后两个目录

时间:2011-11-22 07:04:44

标签: string bash

在bash中,我似乎在一些非常简单的事情上失败了。我有一个字符串变量,它包含目录的完整路径。我想将最后的两个目录分配给另一个字符串。例如,如果我有:

DIRNAME = /a/b/c/d/e

我想:

DIRNAME2 = d/e

我确信有一个简单的bash构造或sed命令会执行它,但它正在逃避我。我有点像basenamedirname的通用版本,它不仅仅返回名称的极端部分。

谢谢! 戴夫

7 个答案:

答案 0 :(得分:12)

DIRNAME="/a/b/c/d/e"
D2=$(dirname "$DIRNAME")
DIRNAME2=$(basename "$D2")/$(basename "$DIRNAME")

或者,在一行中(但要小心所有双引号 - 分割时更容易):

DIRNAME2=$(basename "$(dirname "$DIRNAME")")/$(basename "$DIRNAME")

除非你严重受虐狂,否则不要尝试使用背引号的游戏。如果路径中可能有空格,请在变量名称周围使用双引号。

这几乎适用于任何shell Korn Shell以及Bash。在bash中,还有其他可用的机制 - 其他答案说明了一些选项,尽管expr也是一个老派的解决方案(它也出现在第7版Unix中)。这个使用反引号的代码也适用于Bash和Korn shell - 但不适用于Heirloom Shell(类似于Unix System V Release 2/3/4 shell,IIRC)。

DIRNAME2=`basename "\`dirname \\"$DIRNAME\\"\`"`/`basename "$DIRNAME"`

(两个级别的嵌套并不太糟糕,但它非常糟糕;三个非常棘手!)

测试

当测试应该在路径名中的空格中存在的路径名操作时,值得测试使用包含双空格(而不是单个空格)的名称。例如:

DIRNAME="/a b/ c d /  ee  ff  /  gg  hh  "
echo "DIRNAME=[[$DIRNAME]]"
echo "basename1=[[$(basename "$DIRNAME")]]"
echo "basename2=[[`basename \"$DIRNAME\"`]]"
echo
D2=$(dirname "$DIRNAME")
echo "D2=[[$D2]]"
DIRNAME2=$(basename "$D2")/$(basename "$DIRNAME")
echo "DIRNAME2=[[$DIRNAME2]]"
echo
DIRNAME3=$(basename "$(dirname "$DIRNAME")")/$(basename "$DIRNAME")
echo "DIRNAME3=[[$DIRNAME3]]"
DIRNAME4=`basename "\`dirname \\"$DIRNAME\\"\`"`/`basename "$DIRNAME"`
echo "DIRNAME4=[[$DIRNAME2]]"

该输出是:

DIRNAME=[[/a b/ c d /  ee  ff  /  gg  hh  ]]
basename1=[[  gg  hh  ]]
basename2=[[  gg  hh  ]]

D2=[[/a b/ c d /  ee  ff  ]]
DIRNAME2=[[  ee  ff  /  gg  hh  ]]

DIRNAME3=[[  ee  ff  /  gg  hh  ]]
DIRNAME4=[[  ee  ff  /  gg  hh  ]]

答案 1 :(得分:10)

我更喜欢尽可能多地使用内置函数,以避免创建不必要的进程。因为您的脚本可能在Cygwin或其他过程创建非常昂贵的操作系统下运行。

如果您只想提取两个目录,我认为这不是很长:

base1="${DIRNAME##*/}"
dir1="${DIRNAME%/*}"
DIRNAME2="${dir1##*/}/$base1"

这也可以避免执行其他命令时遇到的特殊字符问题。

答案 2 :(得分:3)

我不知道专门用于修剪路径的方法,但您当然可以使用bash's regular expression matching执行此操作:

DIRNAME=/a/b/c/d/e
if [[ "$DIRNAME" =~ ([^/]+/+[^/]+)/*$ ]]; then
    echo "Last two: ${BASH_REMATCH[1]}"
else
    echo "No match"
fi

注意:为了处理路径中允许但不常见的事情,我在这里使模式比你想象的要复杂一点:它修剪尾部斜杠,并容忍多个(冗余)斜杠在最后两个名字之间。例如,在“/ a / b / c // d //”上运行它将匹配“c // d”。

答案 3 :(得分:2)

我认为使用globs的方式较短,但是:

$ DIRNAME='a/b/c/d/e'
$ LAST_TWO=$(expr "$DIRNAME" : '.*/\([^/]*/[^/]*\)$')
$ echo $LAST_TWO
d/e

答案 4 :(得分:2)

DIRNAME="a/b/c/d/e"
DIRNAME2=`echo $DIRNAME | awk -F/ '{print $(NF-1)"_"$(NF)}'`

DIRNAME2 then has the value d_e

将下划线更改为您想要的任何内容。

答案 5 :(得分:1)

dirname=/a/b/c/d/e
IFS=/ read -a dirs <<< "$dirname"
printf "%s/%s\n" "${dirs[-2]}" "${dirs[-1]}"

答案 6 :(得分:-1)

function getDir() {
echo $1 | awk -F/ '
{
    n=NF-'$2'+1;
    if(n<1)exit;
    for (i=n;i<=NF;i++) {
        printf("/%s",$i);
    }
}'
}

dir="/a/b/c/d/e"
dir2=`getDir $dir 1`
echo $dir2

您可以从此函数的最后一个目录中获取任意数量的目录。 对于你的情况,运行它,

dir2=`getDir $dir 2`;

输出:/ d / e