【问题标题】:Extracting directory name from an absolute path using sed or awk使用 sed 或 awk 从绝对路径中提取目录名称
【发布时间】:2011-12-19 10:57:06
【问题描述】:

我想分割这条线

/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh

/home/edwprod/abortive_visit/bin

使用sedawk 脚本?你能帮忙吗?

【问题讨论】:

  • 上下文是什么?是在文件里吗?你有不止一次出现吗?你……?
  • sed 和 awk 对此太过分了,shell 直接为此提供了实用程序。
  • 如果你使用 bash:为什么不使用dirname 命令?

标签: bash awk sed


【解决方案1】:

目录名

kent$  dirname "/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh"
/home/edwprod/abortive_visit/bin

sed

kent$  echo "/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh"|sed 's#/[^/]*$##'
/home/edwprod/abortive_visit/bin

grep

kent$  echo "/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh"|grep  -oP '^/.*(?=/)'
/home/edwprod/abortive_visit/bin

awk

kent$  echo "/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh"|awk -F'/[^/]*$' '{print $1}'
/home/edwprod/abortive_visit/bin

【讨论】:

  • dirname 的所有这些替代方案中,仅包含基本名称的路径存在问题。在这种情况下,dirname 给出了.grep 版本返回一个空字符串,其他版本为basename
  • awk 调用也会在 foo/bar/ 等情况下失败
【解决方案2】:

可能是命令 dirname 是您搜索的内容?

dirname /home/edwprod/abortive_visit/bin/abortive_proc_call.ksh

或者如果你想要 sed,那么看我的解决方案:

echo /home/edwprod/abortive_visit/bin/abortive_proc_call.ksh | sed 's/\(.*\)\/.*/\1/'

【讨论】:

  • sed-based 解决方案存在路径仅包含基本名称的问题。在这种情况下,dirname 返回.
【解决方案3】:

现在可用于大多数平台和 Unix/Linux shell dirname

dirname /home/edwprod/abortive_visit/bin/abortive_proc_call.ksh

使用dirname 是最简单的方法,但不建议用于跨平台脚本,例如在autoconf 文档http://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.69/html_node/Limitations-of-Usual-Tools.html#Limitations-of-Usual-Tools 的最新版本中。

所以我基于sed 的全功能版本替代dirname

str="/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh"
echo "$str" | sed -n -e '1p' | sed  -e 's#//*#/#g' -e 's#\(.\)/$#\1#' -e 's#^[^/]*$#.#' -e 's#\(.\)/[^/]*$#\1#' -

示例:

它的工作方式类似于dirname:

  • 对于像/aa/bb/cc 这样的路径,它将打印/aa/bb
  • 对于像/aa/bb 这样的路径,它将打印/aa
  • 对于像/aa/bb/ 这样的路径,它也会打印/aa
  • 对于像/aa/ 这样的路径,它将打印/aa
  • 对于像/ 这样的路径,它将打印/
  • 对于像aa 这样的路径,它将打印.
  • 对于像aa/ 这样的路径,它将打印.

即:

  • 它适用于尾随/
  • 它适用于仅包含基本名称的路径,例如 aaaa/
  • 它适用于以/ 开头的路径和/ 本身的路径。
  • 它适用于任何$str,如果它最后包含\n,即使有许多\n
  • 它使用跨平台的 sed 命令
  • 它将/ (// ///) 的所有组合更改为/
  • 当路径包含对当前语言环境无效的换行符和字符时,它无法正常工作。

注意 basename 的替代品可能有用:

echo "$str" | awk -F"/" '{print $NF}' -

【讨论】:

  • 我不敢相信我是三年来唯一一个对此表示赞同的人。为这个令人惊叹的 sed 单线器脱帽致敬。我写了一个更长的版本,并添加了针对dirname 和这个单行的兼容性测试(参见这个answer。单行与dirname 的唯一区别是它去除了额外的前导斜杠(例如“//a/b” -> "/a"),但在某些情况下可能是您想要的,也可能不是。不过,这些仍然是一些令人印象深刻的 sed 忍者技能。
【解决方案4】:

awk + ​​for :

echo "/home/edwprod/abortive_visit/bin/abortive_proc_call.ksh" | awk 'BEGIN{res=""; FS="/";}{ for(i=2;i<=NF-1;i++) res=(res"/"$i);} END{print res}'

【讨论】:

    【解决方案5】:

    我猜,带有 awk 的这段代码将与 dirname 一样完美地工作。

    它非常简单,而且工作成本非常低。祝你好运。

    代码

    $ foo=/app/java/jdk1.7.0_71/bin/java
    $ echo "$foo" | awk -F "/*[^/]*/*$" '
    { print ($1 == "" ? (substr($0, 1, 1) == "/" ? "/" : ".") : $1); }'
    

    结果

    /app/java/jdk1.7.0_71/bin

    测试

    • foo=/app/java/jdk1.7.0_71/bin/java -> /app/java/jdk1.7.0_71/bin
    • foo=/app/java/jdk1.7.0_71/bin/ -> /app/java/jdk1.7.0_71
    • foo=/app/java/jdk1.7.0_71/bin -> /app/java/jdk1.7.0_71
    • foo=/app/ -> /
    • foo=/app -> /
    • foo=fighters/ -> .

    更多

    如果你没有这样的 awk 分隔符,试试这种方式。

    $ echo $foo | awk '{
     dirname = gensub("/*[^/]*/*$", "", "", $0);
     print (dirname == "" ? (substr($0, 1, 1) == "/" ? "/" : ".") : dirname);
     }'
    

    【讨论】:

    • 在像/foo/////bar 这样带有多个斜杠的情况下会失败。一个调整是:awk -F "/?[^/]*/?$" '{gsub("/+","/"); print ($1==""?(substr($0, 1, 1)=="/"?"/":".") : $1); }'
    • @kvantour 不错的建议!但是,我认为这是更好的方法:awk -F "/*[^/]*/*$" '{print ($1==""?(substr($0, 1, 1)=="/"?"/":".") : $1); }' 差异:"/?[^/]*/?$" -> "/*[^/]*/*$" 这涵盖了另一个失败:/foo/////bar///// 谢谢。
    【解决方案6】:

    另外,对于 Kent 的回答,另一种 awk 解决方案是:

    awk 'BEGIN{FS=OFS="/"}{NF--}1'
    

    与肯特提出的疾病相同。以下稍长的 Awk 纠正了所有缺陷:

    awk 'BEGIN{FS=OFS="/"}{gsub("/+","/")}
         {s=$0~/^\//;NF-=$NF?1:2;$0=$0?$0:(s?"/":".")};1' <file>
    

    下表显示了区别:

    | path       | dirname | awk full | awk short |
    |------------+---------+----------+-----------|
    | .          | .       | .        |           |
    | /          | /       | /        |           |
    | foo        | .       | .        |           |
    | foo/       | .       | .        | foo       |
    | foo/bar    | foo     | foo      | foo       |
    | foo/bar/   | foo     | foo      | foo/bar   |
    | /foo       | /       | /        |           |
    | /foo/      | /       | /        | /foo      |
    | /foo/bar   | /foo    | /foo     | /foo      |
    | /foo/bar/  | /foo    | /foo     | /foo/bar  |
    | /foo///bar | /foo    | /foo     | /foo//    |
    

    注意:dirname 是真正可行的方法,除非您必须处理存储在文件中的大量它们。

    【讨论】:

      【解决方案7】:

      我总是对一些单行线的聪明程度感到惊讶。就我而言,我追求的是可读性,所以这是我的实现。这是一个普通的 Bourne shell 脚本,它依赖于模仿 dirname 的 sed 和 grep。

      strip_trailing_slashes() {
        printf "$1" | sed 's/[/]*$//'
      }
      
      # If empty arg, return .
      # If nothing but slashes, return /
      # If no slashes after stripping all trailing slashes, return .
      # Otherwise, return everything up until last path component
      dirname() {
        if [ -z "$1" ]; then printf '.'; return; fi
        if ! $(printf "$1" | grep -q '[^/]'); then printf '/'; return; fi
      
        local s="$(strip_trailing_slashes $1)"
        if ! $(printf "$s" | grep -q '/'); then printf '.'; fi
      
        printf "$(strip_trailing_slashes $(printf $s | sed 's/[^/]*$//'))"
      }
      

      这是一个gist,其中包括针对以下情况的 44 项测试——还针对dirname 进行了兼容性测试。

      Expect '.' for ('""' '.' '.foo' './foo' 'foo' 'foo/' 'foo///' '..' '..foo' '..foo//' '../')
      Expect '/' for ('/' '//' '///')
      Expect 'a/b' for ('a/b/c' 'a/b/c/' 'a/b/c///')
      Expect '/a/b' for ('/a/b/c' '/a/b/c/' '/a/b/c///')
      Expect '//a/b' for ('//a/b/c' '//a/b/c//' '/a/b/c')
      

      几点:

      1. @Роман Коптев 的 one-liner sed version 太棒了(我怎么能成为唯一被点赞的人?)。
      2. 仅有的两个测试失败是由于 oneliner 版本,但它与 dirname 不同。 oneliner 版本将下面的“//a/b/c”转换为“/a/b”,这可能不是你想要的; dirname 不会去除多余的前导斜杠并返回“//a/b”。我选择让我的版本与dirname 兼容。 (没有一个版本在中间分隔路径组件中去除多余的斜线)。

      向@Роман Коптев 致敬 oneliner 版本!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-10-04
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-26
        • 1970-01-01
        相关资源
        最近更新 更多