题 在Unix shell中提取子串的最简单方法是什么?


在Unix shell(使用正则表达式)上提取子字符串的最简单方法是什么?

简单意味着:

  • 功能较少
  • 选择少了
  • 少学习

更新

我意识到正则表达式本身与简单性相冲突,我选择了最简单的一个 cut 作为选择的答案。对于模糊的问题我很抱歉。我更改了标题以更准确地表示此QA的当前状态。


5
2017-09-04 16:08




如果您能描述从哪里提取的内容,将会有所帮助。即使使用像grep和sed这样的复杂工具,简单的事情往往也很简单。 - Sven♦
你的问题含糊不清,过于宽泛。 - Dennis Williamson
问题非常好!但明确的答案与这个问题无关。 - erikbwork


答案:


cut 可能有用:

$ echo hello | cut -c1,3
hl
$ echo hello | cut -c1-3
hel
$ echo hello | cut -c1-4
hell
$ echo hello | cut -c4-5
lo

Shell Builtins对此也有好处,下面是一个示例脚本:

#!/bin/bash
# Demonstrates shells built in ability to split stuff.  Saves on
# using sed and awk in shell scripts. Can help performance.

shopt -o nounset
declare -rx       FILENAME=payroll_2007-06-12.txt

# Splits
declare -rx   NAME_PORTION=${FILENAME%.*}     # Left of .
declare -rx      EXTENSION=${FILENAME#*.}     # Right of .
declare -rx           NAME=${NAME_PORTION%_*} # Left of _
declare -rx           DATE=${NAME_PORTION#*_} # Right of _
declare -rx     YEAR_MONTH=${DATE%-*}         # Left of _
declare -rx           YEAR=${YEAR_MONTH%-*}   # Left of _
declare -rx          MONTH=${YEAR_MONTH#*-}   # Left of _
declare -rx            DAY=${DATE##*-}        # Left of _

clear

echo "  Variable: (${FILENAME})"
echo "  Filename: (${NAME_PORTION})"
echo " Extension: (${EXTENSION})"
echo "      Name: (${NAME})"
echo "      Date: (${DATE})"
echo "Year/Month: (${YEAR_MONTH})"
echo "      Year: (${YEAR})"
echo "     Month: (${MONTH})"
echo "       Day: (${DAY})"

那输出:

  Variable: (payroll_2007-06-12.txt)
  Filename: (payroll_2007-06-12)
 Extension: (txt)
      Name: (payroll)
      Date: (2007-06-12)
Year/Month: (2007-06)
      Year: (2007)
     Month: (06)
       Day: (12)

并且根据上面的Gnudif,总是有sed / awk / perl,因为什么时候变得非常艰难。


9
2017-09-04 16:32



虽然有时您可能需要声明变量只读,但在诸如此类的上下文中很少需要导出变量。做一项任务要简单得多: var=value 没有使用 declare 一点都不 - Dennis Williamson
Wgy是否标记为解决方案?我没有看到正则问题的正则表达式。 - erikbwork
@erikb我选择了这个 cut,因为我把简单性更多地放在简单性上,而不是灵活性。对于正则表达式的模糊问题我很抱歉,但我意识到正则表达式本身就是简单的冲突。 - Eonil
@Eonil然后你介意改变问题标题吗?像我这样寻找正则表达式解决方案的人会遇到这个问题并且找不到解决方案。 - erikbwork
@erikb这听起来很合理。我做的! - Eonil


Unix shell传统上不支持内置的正则表达式。 Bash和Zsh都这样做,所以如果你使用的话 =~ 运算符将字符串与正则表达式进行比较,然后:

你可以从中得到子串 $BASH_REMATCH bash中的数组。

在Zsh,如果 BASH_REMATCH shell选项设置,值在 $BASH_REMATCH 数组,否则它就在 $MATCH/$match 绑定变量对(一个标量,另一个是数组)。如果 RE_MATCH_PCRE 选项设置,然后使用PCRE引擎,否则使用系统regexp库,按照bash进行扩展的regexp语法匹配。

所以,最简单的说:如果你使用bash:

if [[ "$variable" =~ unquoted.*regex ]]; then
  matched_portion="${BASH_REMATCH[0]}"
  first_substring="${BASH_REMATCH[1]}"
fi

如果您不使用Bash或Zsh,则需要使用外部命令时会变得更复杂。


2
2018-01-05 10:08



POSIX bourne shell不是,但是 expr(1) 支持 - ptman
是, expr(1) 是“使用外部命令”的明显例子,但安全地捕获可包含任意字符的值会变得“有趣”。 - Phil P


grep和sed可能是你想要的工具,具体取决于文本的结构。

如果你不知道子串是什么,sed应该做的伎俩,但知道它周围的一些模式。

例如,如果要查找以“#”符号开头的数字子字符串,可以编写如下内容:

sed 's/^.*#\([0-9]\+\)/\1/g' yourfile

grep可以做类似的事情,但问题是您需要对子字符串做什么,以及我们是否正在讨论正常的行尾分隔文本。


2
2017-09-04 16:25



这应该标记为答案。但我不确定为什么我总是需要写 \(...\) 而你似乎并不需要那样做。 - erikbwork
另外,据我记忆,它取决于您使用的引号类型:单引号或双引号。也可能有关于使用的shell的警告(我自己使用tcsh)。 - Gnudiff


还考虑一下 /usr/bin/expr

$ expr substr hello 2 3
ell

您还可以将模式与字符串的开头匹配。

$ expr match hello h
1

$ expr match hello hell
4

$ expr match hello e
0

$ expr match hello 'h.*o'
5

$ expr match hello 'h.*l'
4

$ expr match hello 'h.*e'
2

1
2018-04-18 16:25