【问题标题】:shell command to remove characters after a special character in bash/shell用于删除 bash/shell 中特殊字符后的字符的 shell 命令
【发布时间】:2014-12-30 18:30:20
【问题描述】:

我有文件名

hello_1.0_25.tgz
a_hello_1.25.6_154.tgz
<name>_<name1>.tgz

我需要的输出是

hello_1.0
a_hello_1.25.6
<name>

如何在 bash(或)shell 中获取特殊字符 _ 之前的字符串?

【问题讨论】:

    标签: bash shell sed


    【解决方案1】:

    在 bash 中,这很简单:

    $ f=hello_1.0_25.tgz
    $ echo "${f%_*}"
    hello_1.0
    

    ${f%_*} 只是从变量f 的末尾删除_ 及其后面的任何内容。

    这比使用外部工具的其他方法更简洁,并且在不需要时也可以节省使用额外的过程。

    more tips on string manipulation in bash

    【讨论】:

    • 使用正确的工具完成工作总是好的。好老parameter expansion w/substring extraction。干得好。
    【解决方案2】:

    类似

    sed -r 's/(.*)_.*/\1/'
    

    测试

    $ echo "hello_1.0_25.tgz" | sed -r 's/(.*)_.*/\1/'
    hello_1.0
    $ echo "a_hello_1.25.6_154.tgz" | sed -r 's/(.*)_.*/\1/'
    a_hello_1.25.6
    $ echo "<name>_<name1>.tgz" | sed -r 's/(.*)_.*/\1/'
    <name>
    

    它有什么作用?

    • s替换命令

    • (.*) 匹配任何内容,直到最后一个 _ 。保存在\1

    • _.* 匹配 _,然后是其余的

    • /\1/ 替换为\1,第一个捕获组

    sed -r 's/_[^_]+$//'
    

    测试

    $ echo "hello_1.0_25.tgz" | sed -r 's/_[^_]+$//'
    hello_1.0
    $ echo "a_hello_1.25.6_154.tgz"  | sed -r 's/_[^_]+$//'
    a_hello_1.25.6
    $ echo "<name>_<name1>.tgz"   | sed -r 's/_[^_]+$//'
    <name>
    

    它有什么作用?

    • [^_]+ 匹配除_ 之外的任何内容。 + 将前一个模式量化一次或多次

    • $ 匹配行尾

    • // 替换为空

    【讨论】:

    • 能否请您也解释一下,以便概念清晰?
    • @user3003758 不客气 :) 我已经添加了解释。希望你现在清楚
    【解决方案3】:

    sed 行应该这样做:

    sed 's/_[^_]*$//' 
    

    用你的例子做个小测试:

    kent$  cat f
    hello_1.0_25.tgz
    a_hello_1.25.6_154.tgz
    <name>_<name1>.tgz
    
    kent$  sed 's/_[^_]*$//' f
    hello_1.0
    a_hello_1.25.6
    <name>
    

    awk 肯定也能做到:

    kent$  awk -F_ -v OFS="_" 'NF--' f
    hello_1.0
    a_hello_1.25.6
    <name>
    

    grep,如果你喜欢:

    kent$  grep -Po '.*(?=_[^_]*$)' f
    hello_1.0
    a_hello_1.25.6
    <name>
    

    @Tom Fenech 的 bash 方式也不错。

    【讨论】:

      【解决方案4】:

      substring extraction 略有不同:

      $ m="a_hello_1.25.6_154.tgz"
      $ echo "${m/%_${m/#*_/}/}"
      $ a_hello_1.25.6
      

      基本上是说${m/#*_/} 找到最后一个_之后的文字 = 154.tgz(叫它stuff);然后从字符串的后端删除它,前面加下划线 ${m/%_stuff/}。对于${m/%_${m/#*_/}/}的完整表达。

      【讨论】:

        【解决方案5】:

        试试这个。

        sed 's/\(.*\)_\.*/\1/g' file_name
        

        【讨论】:

          【解决方案6】:

          使用 Bash 正则表达式:

          $ f=hello_1.0_25.tgz
          $ if [[ $f =~ (.*)_.*\.tgz$ ]]; then echo "${BASH_REMATCH[1]}"; fi
          hello_1.0
          

          【讨论】:

            【解决方案7】:

            这个awk 应该这样做:

            awk  -F_ '{$NF="";sub(/_$/,"")}1' OFS=_ file
            hello_1.0
            a_hello_1.25.6
            <name>
            

            -F_ 将字段分隔符设置为 _
            $NF="" 删除最后一个字段。
            sub(/_$/,"") 删除最后一个字段分隔符。
            1 打印出所有行。

            【讨论】:

            • Awk 可能是矫枉过正(如果你不解释的话)。
            • awk -F_ 'NF--' OFS=_ file
            • @Jidder 如果有空行,这将失败,因为NF 变为负数。这将正常工作,但会删除空白行 awk -F_ 'NF&amp;&amp;NF--' OFS=_ file
            • OP 不是在文件名上这样做吗?几乎不可能有空行。如果确实如此,并且您想保留它们,则可以使用awk -F_ '!NF||NF--' OFS=_ file
            • @Pureferret 我同意并添加了评论。 awk 在某些情况下可能比sed 中的复杂regex 更容易理解
            猜你喜欢
            • 2023-01-24
            • 1970-01-01
            • 2021-06-26
            • 1970-01-01
            • 2011-06-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-03-10
            相关资源
            最近更新 更多