挑战:仅用于shell-assignment配置行的Regex-only tokenizer

时间:2011-12-28 16:44:43

标签: regex tokenize

我问了原始问题here,并通过混合Ruby和正则表达式得到了实际的回答。现在,我的纯粹主义者想要知道:可以这可以在正则表达式中完成吗?我的直觉说它可以。有一个ABNF浮动为bash 2.0,虽然它不包括字符串转义。

规范

给定输入行是(1)来自bash风格脚本的变量(“key”)赋值,或者(2)来自典型配置文件(如postgresql.conf)的键值设置,此正则表达式(或一对regexen)应该以这样的方式捕获键和值,以便我可以使用这些捕获来替换该键的新值。

您可以为shell风格和配置风格的行使用不同的正则表达式;呼叫者将知道使用哪个。

这里会有50分的赏金。我不能再加两天的赏金,所以在此之前我不会接受答案,但你可以立即开始回答。您可以获得积分:

  • 可读性(命名捕获组,定义通过?(DEFINE)或{0})
  • 使用单个正则表达式而不是两个
  • 教我一些关于DFA的事。
  • 正则表达式(如果相关)
  • 获得投票
  • 首先使用技术

示例:

给出输入

export RAILS_ENV=production

我应该能用Ruby编写:

match = THE_REGEX.match("export RAILS_ENV=production")
newline = "export #{match[:key]}=#{match[:value]}"

测试用例:shell样式

RAILS_ENV=development     # Don't forget to change this for TechCrunch
HOSTNAME=`cat /etc/hostname`
plist=`cat "/Applications/Sublim\`e Text 2.app/Content's/Info.plist"`

# Optional bonus input: "#" present in the string
FORMAT="  ##0.00 passe\`" #comment

测试用例:配置样式

listen_addresses = 127.0.0.1 #localhost only by default
# listen_addresses = 0.0.0.0 commented out, should not match

出于这个挑战的目的,“正则表达式”和“正则表达式”意思相同,两者都可以引用您喜欢的任何常见风格,但我更喜欢Ruby 1.9兼容。

1 个答案:

答案 0 :(得分:2)

我不确定完整的规格以及您在价值捕获组中的确切需求,但这应该适用于您的测试用例:

/
^\s*+

(?:export\s++)?
(?<key>\w++)

\s*+
=
\s*+

(?<value>
  (?>  "(?:[^"\\]+|\\.)*+"
  |    '(?:[^'\\]+|\\.)*+'
  |    `(?:[^`\\]+|\\.)*+`
  |    [^#\n\r]++
  )
)

\s*+
(?:#.*+)?
$
/mx;

处理带有转义符的注释和引号。

Perl / PCRE的味道和引用。


Perl中的用法示例:

my $re = qr/
    ^\s*+

    (?:export\s++)?
    (?<key>\w++)

    \s*+
    =
    \s*+

    (?<value>
      (?>  "(?:[^"\\]+|\\.)*+"
      |    '(?:[^'\\]+|\\.)*+'
      |    `(?:[^`\\]+|\\.)*+`
      |    [^#\n\r]++
      )
    )

    \s*+
    (?:\#.*+)?
    $
/mx;

my $str = <<'_TESTS_';
RAILS_ENV=development     # Don't forget to change this for TechCrunch
HOSTNAME=`cat /etc/hostname`
plist=`cat "/Applications/Sublim\`e Text 2.app/Content's/Info.plist"`

# Optional bonus input: "#" present in the string
FORMAT="  ##0.00 passe\`" #comment

listen_addresses = 127.0.0.1 #localhost only by default
# listen_addresses = 0.0.0.0 commented out, should not match

TEST="foo'bar\"baz#"
TEST='foo\'bar"baz#\\'
_TESTS_


for(split /[\r\n]+/, $str){
    print "line: $_\n";
    print /$re/? "match: $1, $2\n": "no match\n";
    print "\n";
}

输出:

line: RAILS_ENV=development     # Don't forget to change this for TechCrunch
match: RAILS_ENV, development

line: HOSTNAME=`cat /etc/hostname`
match: HOSTNAME, `cat /etc/hostname`

line: plist=`cat "/Applications/Sublim\`e Text 2.app/Content's/Info.plist"`
match: plist, `cat "/Applications/Sublim\`e Text 2.app/Content's/Info.plist"`

line: # Optional bonus input: "#" present in the string
no match

line: FORMAT="  ##0.00 passe\`" #comment
match: FORMAT, "  ##0.00 passe\`"

line: listen_addresses = 127.0.0.1 #localhost only by default
match: listen_addresses, 127.0.0.1

line: # listen_addresses = 0.0.0.0 commented out, should not match
no match

line: TEST="foo'bar\"baz#"
match: TEST, "foo'bar\"baz#"

line: TEST='foo\'bar"baz#\\'
match: TEST, 'foo\'bar"baz#\\'