分割字符串、提取字符并重新组合

分割字符串、提取字符并重新组合

wva/sia/e1我有、bct/e2、形式的字符串sv/de/e11。它总是 <Part1>/e<NUM><Part1>/<Part2>/e<NUM>。我想要通过保留各部分的首字母并去掉斜线和 e 来缩短字符串:

wva/sia/e1 > ws1
bct/e2 > b2
sv/de/e11 > sd11

我如何在 sh 脚本中做到这一点?

编辑:该字符串代表作业名称:

[...]
job_name=$1 # e.g. 'wva/sia/e1'
job_name=cut_name(job_name) # e.g. 'ws1'
[...]

答案1

形式为脚本正如你所要求的:

#!/usr/bin/env python3
import sys

# read the input, split by /
st = sys.argv[1].split("/")
# get the first char of all sections *but* the last one
# add the last *from* the first character
print("".join([s[0] for s in st][:-1])+st[-1][1:])

请注意,这适用于任何长度,例如:

wva/sia/bct/wva/sia/e1

会变成

wsbws1

只要最后一部分以/e<num>

使用

  1. 将脚本复制到一个空文件中,另存为rearrange.py
  2. 使用字符串作为参数运行它,例如:

    python3 /path/to/rearrange.py wva/sia/e1
    
    > ws1
    

解释

脚本本身已经解释了一切,而且也进行了注释。

答案2

Bash 4.3 单行命令

假设我们不需要完整的脚本。Bash 具有足够的功能,允许我们使用一行代码。下面是其中的一行:

bash-4.3$ (read -r var ;IFS='/'; printf "%c" ${var%/*};echo ${var##*[^0-9]}) <<<  "sv/de/e11"
sd11

怎么了 ?

  • 一切都发生在子 shell 中,因此( )围绕整个命令
  • 我们在这里使用字符串<<<发送输入,子shell命令通过它获取read -r var并存储到var变量中
  • 我们设置IFS='/'子 shellvar在分隔符处拆分成单独的项/。这对于分词很重要。
  • 接下来我们使用后缀删除${var%/*}来删除 之前的最后一部分/。在上面的例子中,它将是e11
  • printf "%c"${var%/*}会看到由于上面提到的单词拆分和后缀删除而产生的结果sv de(是的,很神奇)。由于printf单词的格式,%c将仅打印第一个字符,但它将对收到的每个命令行参数都这样做,因此sv de它将输出sd。打印时没有换行符,因此看起来好像字符是按顺序输入的
  • echo ${var##*[^0-9]}利用前缀删除来删除给定输入字符串中的所有非数字字符,从而仅获取最后一位数字

还有另一种单行方法,对于类似 C 语言的程序员来说,这种方法更加明确和自然。

bash-4.3$ (read -r inp;IFS='/';arr=( $inp ); for ((i=0;i<$(( ${#arr[@]} -1 ));i++));do printf "%s" ${arr[$i]:0:1};done;printf "%s\n" ${inp##*[^0-9]}) <<<  "sv/de/e11"
sd11

这是什么魔法?解释如下:

  • 所有事情都发生在子shell中,因此()围绕整个命令。
  • 我们使用 here-string<<<将我们想要的项目发送到命令的标准输入流中,命令通过read -r inp命令获取它并将其存储到inp变量中
  • 接下来我们改变IFS变量,以便我们可以将所有内容分解为一个数组。
  • 我们使用 C 风格的 for 循环遍历所有项,直到倒数第二项for ((initial condition; test condition; post condition)) ; do ... done
  • $(( ${#arr[@]} - 1 )) 是算术扩展,我们从数组的长度中减去 1${#arr[@]}
  • printf "%s" ${arr[$i]:0:1}允许我们使用参数扩展,其中我们只打印每个项目的第一个字符,并且printf "%s"不带换行符打印,因此看起来我们在同一行上打印每个字母。
  • 最后,循环结束后,我们获取原始输入文本,并使用前缀删除功能删除所有非数字内容${#*[^0-9]}

脚本方法

由于问题要求使用 shell 脚本,这里是bash4.3 中的一个脚本,它与上面的方法几乎相同,但更明确:

#!/bin/bash
IFS='/'
items=( $1 )
counter=1
for i in ${items[@]}
do
    if [ $counter -eq ${#items[@]}  ];
    then
        # note the space before -1
        printf "%s\n" "${i##*[^0-9]}"
    else
        printf "%s" "${i:0:1}"
    fi
    counter=$(($counter + 1)) 
done

其工作方式如下:

  • 在命令行上给定一个字符串作为参数,我们将内部字段分隔符设置为/,并允许 bash 执行单词拆分以将字符串分解为名为的数组items
  • 我们迭代数组中的所有项目${items[@]},同时使用计数器变量跟踪我们所处的项目,并了解数组中的项目数量(部分${#items[@]})。
  • if-statement使得我们可以从每个项目中挑选出特定的字符。使用参数扩展,第一个字符通过${i:0:1}。使用最长前缀删除${variable##prefix},我们从中的最后一个字符串中删除所有非数字字符printf "%s\n" "${i##*[^0-9]}"

实际操作如下:

$ ./shorten_string.sh "wva/sia/e1"                         
ws1
$ ./shorten_string.sh "bct/e2"                             
b2
$ ./shorten_string.sh  "sv/de/e11"                     
sd11

答案3

好的,这不是脚本,但你可以把它放在脚本中(这也是非常不优雅的,因为我无法在一个命令中处理这两种形式)

$ sed -r 's:(.).*/(.).*/e([0-9]+):\1\2\3:;s:(.).*/e([0-9]+):\1\2:' file
ws1
b2
sd11

笔记

  • -r使用 ERE
  • s:old:new:old用。。。来代替new
  • .*任意数量的任意字符
  • (.)在此位置保存一个字符
  • ([0-9]+)请在此处保存至少一个数字
  • ;分隔命令,就像在 shell 中一样
  • \1反向引用使用以下方式保存的字符()

相关内容