一个Linux Shell脚本问题

我在Linux Shell中有一个由点分隔的string

$example=This.is.My.String 

我要

1.在最后一个点之前添加一些string,例如,我想在最后一个点之前添加“Good.Long”,所以我得到:

 This.is.My.Goood.Long.String 

拿到最后一个点之后的部分,我会得到

 String 

3.除了最后一个点之外,将点转成下划线,所以我会得到

 This_is_My.String 

如果你有时间,请稍微解释一下,我还在学习正则expression式。

非常感谢!

我不知道'Linux Shell'是什么意思,所以我会认为bash 。 这个解决方案也可以在zsh 等中使用

 example=This.is.My.String before_last_dot=${example%.*} after_last_dot=${example##*.} echo ${before_last_dot}.Goood.Long.${after_last_dot} This.is.My.Goood.Long.String echo ${before_last_dot//./_}.${after_last_dot} This_is_My.String 

临时变量before_last_dotafter_last_dot应该解释我对%##运算符的使用。 / /,我也认为是不言自明的,但我很乐意澄清,如果你有任何问题。

这不使用sed (或甚sed则表达式),但bash的内置参数替换。 我宁愿每个脚本只使用一种语言,尽可能少的分叉:-)

其他用户对#1和#2给出了很好的答案。 #3的一些答案有一些缺点。 在一种情况下,您必须运行两次替代。 另一方面,如果你的字符串有其他下划线,他们可能会被破坏。 这个命令一劳永逸,只影响点:

 sed 's/\(.*\)\./\1\n./;h;s/[^\n]*\n//;x;s/\n.*//;s/\./_/g;G;s/\n//' 
  1. 它通过插入换行符来分割最后一个点之前的行,并将结果复制到保持空间中:

     s/\(.*\)\./\1\n./;h 
  2. 从模式空间中复制去除包括换行符在内的所有内容,并交换占位空间和模式空间:

     s/[^\n]*\n//;x 
  3. 从现在在模式空间的副本中删除包括换行符在内的所有内容

     s/\n.*// 
  4. 将模式空间中的所有点更改为下划线,并将保留空间附加到模式空间的末尾

     s/\./_/g;G 
  5. 去除追加操作添加的换行符

     s/\n// 

然后sed脚本完成并输出模式空间。

在每个编号的步骤结束时(有些由两个实际步骤组成):

一步模式空间举行空间

  1. This.is.My \n .String This.is.My \n .String

  2. This.is.My \n .String .String

  3. 这是我的.String

  4. This_is_My \n .String .String

  5. This_is_My.String .String

  1. 这也是两个版本:
    • 复杂: sed 's/\(.*\)\([.][^.]*$\)/\1.Goood.Long\2/'
    • 简单: sed 's/.*\./&Goood.Long./' – 感谢Dennis Williamson
  2. 你想要什么?
    • 复杂: sed 's/.*[.]\([^.]*\)$/\1/'
    • 更简单: sed 's/.*\.//' – 谢谢, glenn jackman 。
  3. sed 's/\([^.]*\)[.]\([^.]*[.]\)/\1_\2/g'

有了3,一般来说,可能需要至少运行两次替代品(全部)。

说明

请记住,在sed ,符号\(...\)是可以在替换文本中引用为' \1 '或类似符号的'capture'。

  1. 将所有内容捕获到一个以点开始的字符串,然后是一系列非点(您也可以捕获)。 取而代之的是最后一个点,新材料和最后一个点之前的内容。

  2. 忽略最后一个点之后的所有内容,然后捕获一系列非点; 只取代捕获。

  3. 查找并捕获一系列非点,一个点(未捕获),后跟一系列非点和一个点; 用下划线替换第一个点。 这是在全球范围内完成的,但第二次和随后的比赛不会触及任何已经匹配的东西。 因此,我认为你需要ceil(log 2 N)pass,其中N是要被替换的点数。 一个通过1点代替; 两个通行证处理2或3; 4-7次三次传球,依此类推。

这是一个使用Bash的正则表达式匹配(Bash 3.2或更高版本)的版本。

 [[ $example =~ ^(.*)\.(.*)$ ]] echo ${BASH_REMATCH[1]//./_}.${BASH_REMATCH[2]} 

这是一个使用IFS (内部字段分隔符)的Bash版本。

 saveIFS=$IFS IFS=. array=($e) # * split the string at each dot lastword=${array[@]: -1} unset "array[${#array}-1]" # * IFS=_ echo "${array[*]}.$lastword" # The asterisk as a subscript when inside quotes causes IFS (an underscore in this case) to be inserted between each element of the array IFS=$saveIFS 

*在这些步骤之后使用declare -p array来查看数组的外观。

1。

 $ echo 'This.is.my.string' | sed 's}[^\.][^\.]*$}Good Long.&}' This.is.my.Good Long.string 

之前:一个点,然后没有点,直到结束。 之后:显而易见,&与第一部分相匹配

2。

 $ echo 'This.is.my.string' | sed 's}.*\.}}' string 

sed贪婪匹配,所以它会尽可能扩展第一个闭包(。*),即最后一个点。

3。

 $ echo 'This.is.my.string' | tr . _ | sed 's/_\([^_]*\)$/\.\1/' This_is_my.string 

将所有点转换为_,然后将最后一个_转换为点。

(注意:这会将“This.is.my.string_foo”变成“This_is_my_string.foo”,而不是“This_is_my.string_foo”)

如果你使用Awk并且有点创意,你根本不需要正则表达式(那些复杂的东西伤害了我的眼睛!)。

 1. echo $example| awk -v ins="Good.long" -F . '{OFS="."; $NF = ins"."$NF;print}' 

这是什么:
-v ins =“Good.long”告诉awk以“Good.long”为内容创建一个名为“ins”的变量,
-F 。 告诉awk使用点作为输入的字段的分隔符,
-OFS告诉awk使用这个圆点作为你的字段的分隔符作为输出,
NF是字段的数量,所以$ NF代表最后一个字段,
$ NF = …部分取代了最后一个字段,它将当前的最后一个字符串追加到要插入的内容(前面声明的名为“ins”的变量)。

 2. echo $example| awk -F . '{print $NF}' 

$ NF是最后一场,就这样!

 3. echo $example| awk -F . '{OFS="_"; $(NF-1) = $(NF-1)"."$NF; NF=NF-1; print}' 

这里我们必须有创意,因为Awk AFAIK不允许删除字段。 当然,我们将输出字段分隔符设置为下划线。

$(NF-1)= $(NF-1)“。”$ NF:首先,我们用倒数第二个字段替换最后一个字段。
然后,我们欺骗awk,使它认为字段数等于字段数减1,因此删除最后一个字段!

注意你不能说$ NF =“”,因为那么它会显示两个下划线。