【问题标题】:How to remove last n characters from a string in Bash?如何从 Bash 中的字符串中删除最后 n 个字符?
【发布时间】:2015-02-23 20:34:10
【问题描述】:

我在 Bash 脚本中有一个变量 var,其中包含一个字符串,例如:

echo $var
"some string.rtf"

我想删除这个字符串的最后 4 个字符并将结果分配给一个新变量var2,这样

echo $var2
"some string"

我该怎么做?

【问题讨论】:

标签: bash


【解决方案1】:

您可以这样做(在 bash v4 及更高版本中):

#!/bin/bash

v="some string.rtf"

v2=${v::-4}

echo "$v --> $v2"

注意:macos 默认使用 bash 3.x

【讨论】:

  • 它是bash 4+,但在早期版本中您可以使用稍长的${v::${#v}-4}
  • 对我不起作用,bash 说“替换错误”
  • 出于某种原因,在 zsh ${v::-4} croaks "zsh: 应有右大括号"。但是@fredtantini 在${v:0:-4} 下方的回答效果很好。
  • 错误:-2:子字符串表达式
  • @PierreD 节后,我首先在错误的方向搜索了右括号问题,并试图逃避括号。但是 zsh 只需要一个起始索引。这也解释了两个双冒号。感谢 anishane 在您对 fredtantinis 回答的评论中。
【解决方案2】:

要从字符串末尾删除四个字符,请使用${var%????}

要删除包含最后一个 . 之后的所有内容,请使用 ${var%.*}

【讨论】:

  • 纯 shell 答案,将在 BASH 3.x 中工作,在许多由于许可问题而拒绝实施 BASH 4.x 的系统上发现。
  • 这很酷,因为它可以抵抗较短的字符串;然后索引偏移变体失败。
  • 适用于 bash 3.2.25 - 最好的答案 - 正是我想要的
  • 优秀的答案。去掉字符串前面的内容怎么样?
  • @user2023370 查阅文档应该会发现有一个相应的参数替换${var#????}
【解决方案3】:

首先,最好明确说明您的意图。因此,如果您知道字符串以.rtf 结尾,并且想要删除该.rtf,则可以使用var2=${var%.rtf}。这种方法的一个潜在有用的方面是,如果字符串.rtf结尾,它根本不会改变; var2 将包含 var 的未修改副本。

如果您想删除文件名后缀但不知道或不关心它究竟是什么,您可以使用var2=${var%.*} 删除以最后一个. 开头的所有内容。或者,如果您只想将所有内容保持在 first . 之前,但不包括 var2=${var%%.*},则可以使用 var2=${var%%.*}。如果只有一个.,这些选项具有相同的结果,但如果可能有多个,您可以选择要从字符串的哪一端开始工作。另一方面,如果字符串中根本没有.var2 将再次成为var 的未更改副本。

如果您真的想始终删除特定 数字 个字符,这里有一些选项。

您专门标记了这个bash,所以我们将从内置bash 开始。最有效的一个是我上面使用的相同的后缀删除语法:删除四个字符,使用var2=${var%????}。或者仅在第一个是点时删除四个字符,使用var2=${var%.???},类似于var2=${var%.*},但仅在点后面的部分正好是三个字符时删除后缀。如您所见,要以这种方式计算字符,您需要删除每个未知字符一个问号,因此对于较大的子字符串长度,这种方法变得笨拙。

较新的 shell 版本中的一个选项是子字符串提取:var2=${var:0:${#var}-4}。在这里,您可以用任何数字代替 4 以删除不同数量的字符。 ${#var} 被字符串的长度替换,所以这实际上是要求提取并保留 (length - 4) 个从第一个字符开始的字符(在索引 0 处)。使用这种方法,您将失去仅当字符串与模式匹配时才进行更改的选项;无论字符串的实际值是什么,副本都将包含除最后四个字符之外的所有字符。

你可以省略起始索引;它默认为 0,因此您可以将其缩短为 var2=${var::${#var}-4}。事实上,较新版本的 bash(特别是 4+,这意味着 MacOS 附带的那个将不起作用)将负长度识别为要停止的字符的索引,从字符串的末尾开始倒数。因此,在这些版本中,您也可以摆脱字符串长度表达式:var2=${var::-4}

如果您实际上不是在使用 bash,而是在使用其他 POSIX 类型的 shell,则使用 % 删除基于模式的后缀仍然可以工作 - 即使在普通的旧破折号中,基于索引的子字符串提取不会。 Ksh 和 zsh 都支持子串提取,但需要显式的 0 开始索引; zsh 也支持负端索引,而 ksh 需要长度表达式。请注意,zsh 索引 arrays 从 1 开始,但如果您使用此 bash 兼容语法,则索引 strings 从 0 开始。但是 zsh 还允许您将标量参数视为字符数组,在这种情况下,子字符串语法使用基于 1 的计数并将开始和(包括)结束位置放在括号中,用逗号分隔:var2=$var[1,-5].

您当然可以运行一些实用程序来修改字符串并通过命令替换捕获其输出,而不是使用内置的 shell 参数扩展。有几个命令可以工作;一个是var2=$(sed 's/.\{4\}$//' <<<"$var")

【讨论】:

  • 那 ????是一把双刃剑,但它非常适合打开 /branches 文件夹的 .git 存储库。 __repo=github.com/me/something.git # REM 现在去检查 __repo 分支。 ???删除 .git open "${__repo%????}/branches"
  • 很高兴能提供帮助,但使用${__repo%.git} 可能会更明确,因为您确切知道要删除哪四个字符。如果字符串 没有.git 结尾,这也不会删除任何内容,因此即使 __repo 的值由于某种原因不包含后缀也可以。
  • @mark reed cut -c -4 根本不像你想的那样。它只打印字符串的第一个到第四个字符,这里就是some。您无法通过 cut 实现此目的。
  • 谢谢,@Atralb。我必须用恰好有 8 个字符长的字符串进行测试。已更新。
【解决方案4】:

对我有用的是:

echo "hello world" | rev | cut -c5- | rev
# hello w

但我用它来修剪文件中的行,这就是它看起来很尴尬的原因。真正的用途是:

cat somefile | rev | cut -c5- | rev

cut 只能让您从某个起始位置进行修剪,如果您需要可变长度的行,这很糟糕。所以这个解决方案反转(rev)字符串,现在我们关联到它的结束位置,然后使用提到的cut,并反转(再次,rev)它回到原来的顺序。

【讨论】:

  • 这是不正确的,也不是对所问内容的回答。 rev 反转行,而不是字符串。 echo $SOME_VAR | rev | ...可能不会像人们期望的那样行事。
  • @Mohammad 因为broken quoting, 一行。
  • 我想你可以简化一下:echo "hello world" | cut -c-7echo "hello world" | rev | cut -c5- | rev 产生相同的输出。
  • 对我来说,它工作得很好——无论是在管道中还是在变量中,就像var2=$(echo $var | rev | cut -c5- | rev) 这样的东西,但是是的,从技术上讲,这个是在线工作的。如果您不想将每一行作为单独的字符串处理,则多行可能无法按预期工作。
【解决方案5】:

使用Variable expansion/Substring replacement

${var/%模式/替换}

如果 var 的后缀匹配 Pattern,则用 Replacement 替换 Pattern。

所以你可以这样做:

~$ echo ${var/%????/}
some string

或者,

如果你总是有相同的 4 个字母

~$ echo ${var/.rtf/}
some string

如果总是以.xyz结尾:

~$ echo ${var%.*}
some string

也可以使用字符串的长度:

~$ len=${#var}
~$ echo ${var::len-4}
some string

或者干脆echo ${var::-4}

【讨论】:

  • len=${#var}; echo ${var::len-4} 可以缩短为echo ${var:0:-4} :-) 编辑:或者正如@iyonizer 指出的那样,只是echo ${var::-4} ...
  • 回声 ${var::${#var}-4};
【解决方案6】:

你可以使用 sed,

sed 's/.\{4\}$//' <<< "$var"

示例:

$ var="some string.rtf"
$ var1=$(sed 's/.\{4\}$//' <<< "$var")
$ echo $var1
some string

【讨论】:

  • 以及如何将结果分配给var2
  • 这很好,因为它可以像这样在一行上工作... | sed 's/.\{4\}$//'.不使用变量也可以使用
【解决方案7】:

我尝试了以下方法,它对我有用:

#! /bin/bash

var="hello.c"
length=${#var}
endindex=$(expr $length - 4)
echo ${var:0:$endindex}

输出:hel

【讨论】:

  • 这是完美的。我一直在寻找这个确切的答案很长一段时间。这是迄今为止我发现的与 python 的可迭代切片功能最接近的代码。
  • 正是我想要的。谢谢。
【解决方案8】:

希望以下示例对您有所帮助,

echo ${name:0:$((${#name}-10))} --> ${name:start:len}

  • 在上述命令中,name 是变量。
  • start 是字符串的起点
  • len 是必须删除的字符串长度。

例子:

    read -p "Enter:" name
    echo ${name:0:$((${#name}-10))}

输出:

    Enter:Siddharth Murugan
    Siddhar

注意:Bash 4.2 增加了对负子字符串的支持

【讨论】:

  • 这比 Etan Reisner 的回答中与 Bourne 兼容的简单模式替换要冗长得多。
  • 我还必须使用echo "{${name:0:${#name} - 1}}" 删除最后一个字符并避免“子字符串表达式
  • 这个答案简单易懂,与Python的语法类似。很好。
【解决方案9】:

在这种情况下,您可以使用 basename 假设您要删除的文件具有相同的后缀。

例子:

basename -s .rtf "some string.rtf"

这将返回“一些字符串”

如果您不知道后缀,并希望它删除所有内容,包括最后一个点:

f=file.whateverthisis
basename "${f%.*}"

输出“文件”

% 表示砍,.是你要砍的,*是通配符

【讨论】:

    【解决方案10】:

    这也可以完成工作:

    ... | head -c -1
    
    -c, --bytes=[-]NUM
                  print the first NUM bytes of each file; with the leading '-', print all but the last NUM bytes of each file
    

    【讨论】:

    • echo "$v" | head -c -1 导致带有 BSD 头的“头:非法字节计数 -- -1”。使用 GNU 头,(ghead),echo "$v" | ghead -c -1 会导致 "some string.rtf%";没有删除任何字符。 ghead -c -1 &lt;&lt;&lt;'some string.rtf' 也不会删除任何字符。最后,确认没有恶作剧..wc -c &lt;&lt;&lt;"$v"; wc -c &lt;&lt;&lt;"$(ghead -c -1 &lt;&lt;&lt;"$v")" 表明两者都有 16 个字节。您必须使用head/ghead -c -2。对于那些感兴趣的人,ghead --version 会生成“head (GNU coreutils) 9.0”
    【解决方案11】:

    这通过计算字符串的大小对我有用。
    很容易你需要回显你需要返回的值,然后像下面这样存储它

    removechars(){
            var="some string.rtf"
            size=${#var}
            echo ${var:0:size-4}  
        }
        removechars
        var2=$?
    

    一些字符串

    【讨论】:

      猜你喜欢
      • 2011-05-11
      • 1970-01-01
      • 1970-01-01
      • 2018-12-03
      • 2014-11-08
      • 1970-01-01
      • 2021-12-11
      • 2011-11-18
      • 1970-01-01
      相关资源
      最近更新 更多