【问题标题】:Extracting a string between last two slashes in Bash在 Bash 中提取最后两个斜杠之间的字符串
【发布时间】:2021-12-20 03:43:48
【问题描述】:

我知道这可以使用正则表达式轻松完成,就像我在 https://stackoverflow.com/a/33379831/3962126 上回答的那样,但是我需要在 bash 中执行此操作。

所以我在 Stackoverflow 上找到的最接近的问题是 bash: extracting last two dirs for a pathname,但不同的是如果

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

那我需要提取

d

【问题讨论】:

  • $(basename $(dirname "$DIRNAME")) 是否满足您的要求?如果做不到这一点,x=${DIRNAME%/*}; echo ${x##*/} 会完成您需要的工作吗?当心退化的情况。
  • 我在下面发布答案后看到了您的评论。是的,它确实。我真的不明白这个命令如何/为什么提取最后两个斜杠之间的字符串,但我不在乎它对我有用。
  • dirname 命令删除文件名的最后一个组件,删除 /e; basename 命令删除除了文件名的最后一个部分之外的所有内容,删除 /a/b/c,只留下 d
  • @Jonathan,请注意解决方案x=${DIRNAME%/*}; echo ${x##*/} 也与此处的d 匹配:DIRNAME=a/b,我认为这不是 OP 想要的。
  • @JonathanLeffler,这有点错误——你也需要在目录名扩展周围加上引号。

标签: bash


【解决方案1】:

这可能相对较长,但执行起来也比大多数前面的答案快得多(除了 zsh-only 和 j.a. 的答案),因为它只使用 bash 内置的字符串操作并且不使用子 shell 扩展:

string='/a/b/c/d/e'  # initial data
dir=${string%/*}     # trim everything past the last /
dir=${dir##*/}       # ...then remove everything before the last / remaining
printf '%s\n' "$dir" # demonstrate output

printf 在上面使用是因为echo 不能对所有值都可靠地工作(想想它在带有/a/b/c/-n/e 的 GNU 系统上会做什么)。

【讨论】:

  • 这可能是最好的答案,因为它也严格符合 POSIX。
  • 这个@gniourf 的唯一问题是它还匹配最后一个斜杠之前的任何字符串(例如string="d/e" 匹配d)并且OP 要求最后一个两个之间的字符串 斜线。
  • 如果没有斜杠,它甚至会打印任何字符串(例如:string=hello -> 打印hello)。
  • @ja.a.,我不认为这两种情况都是错误——两者都超出了规范中隐含的假设。如果有人想放入 if [[ $string = */*/* ]]; then ... fi 包装此代码以避免它们两者,微不足道。
  • 应该注意的是,这个解决方案的性能明显优于@j.a。在规模上。即使进行了上述检查,我看到路径 /home/a/b/c/d/e/f/g.txt 上的 1,000,000 次迭代约为 1.07 秒,而正则表达式替换解决方案约为 7.57 秒。
【解决方案2】:

这里是纯bash 解决方案:

[[ $DIRNAME =~ /([^/]+)/[^/]*$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"

与其他一些答案相比:

  • 它匹配最后 两个 斜线之间的字符串。因此,例如,如果 DIRNAME=d/ed 不匹配。
  • 更短更快速(只使用内置函数,不创建子进程)。
  • 支持最后两个斜杠之间的任何字符(请参阅Charles Duffyanswer 了解更多信息)。

还要注意这不是在bash 中分配变量的方法:

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

那些空格是错误的,所以删除它们:

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

【讨论】:

    【解决方案3】:

    使用awk

    echo "/a/b/c/d/e" | awk -F / '{ print $(NF-1) }' # d
    

    编辑:当路径包含换行符时这不起作用,并且当斜杠少于两个时仍然给出输出,请参见下面的 cmets。

    【讨论】:

    • 这也匹配 d 这里:DIRNAME=a/b 并且 OP 要求最后一个 两个斜杠之间的字符串。
    • 如果没有斜杠,它甚至会打印任何字符串(例如:echo hello | awk -F / '{ print $(NF-1) }' -> 打印hello)。
    • ...当文件名包含文字换行符时给出不正确的结果。
    【解决方案4】:

    使用sed

    如果要获取第四个元素

    DIRNAME="/a/b/c/d/e"
    echo "$DIRNAME" | sed -r 's_^(/[^/]*){3}/([^/]*)/.*$_\2_g'
    

    如果你想获取最后一个元素

    DIRNAME="/a/b/c/d/e"
    echo "$DIRNAME" | sed -r 's_^.*/([^/]*)/[^/]*$_\1_g'
    

    【讨论】:

    • 与使用内置字符串操作相比效率非常低。 (尽管 awk 答案和基本名称/目录名称也是如此)。
    • 另外,由于缺少引号引起的错误 - 看看如果您的 DIRNAME 包含一个实际上匹配任何内容的 glob 表达式或空格运行会发生什么。
    • 是的,是更好的字符串操作,只是,sed 是另一种解决方案.....其中测试用例失败的例子?
    • 考虑例如DIRNAME='/a/b/c/ * d * /e',其中空格和文字星号是目录名称的一部分。
    • 如果DIRNAME 中有换行符也会表现得很奇怪。
    【解决方案5】:

    天哪,也许这很明显,但最初对我来说不是。我得到了正确的结果:

    dir=$(basename -- "$(dirname -- "$str")")
    echo "$dir"
    

    【讨论】:

    • 正确的写法应该是dir=$(basename "$(dirname "$str")")
    • ...否则,当您的字符串包含空格、匹配文件的 glob 表达式等时,您会遇到错误。
    • ...另外,这已经够慢了,你不想把它放在一个运行了数百或数千次的内部循环中;命令替换(即$(...) 语法)会严重影响性能。
    【解决方案6】:

    使用 zsh 参数替换也很酷

    echo ${${DIRNAME%/*}##*/}
    

    我认为它也比双 $() 更快,因为它不需要任何子进程。

    基本上它先切掉右侧,然后再切掉剩下的所有左侧。

    【讨论】:

    • 哪个版本的 Bash 支持嵌套替换? zsh 有,但 Bash AFAIK 没有——但我也是来这里学习的。
    猜你喜欢
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 2012-07-07
    • 2020-10-15
    相关资源
    最近更新 更多