【问题标题】:Regular expression - replace all spaces in beginning of line with periods正则表达式 - 用句点替换行首的所有空格
【发布时间】:2018-03-15 06:15:41
【问题描述】:

我不在乎我是否通过 vim、sed、awk、python 等来实现这一点。我尝试了所有,但无法完成。

对于这样的输入:

top           f1    f2    f3
   sub1       f1    f2    f3
   sub2       f1    f2    f3
      sub21   f1    f2    f3
   sub3       f1    f2    f3

我想要:

top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

然后我想在 Excel 中加载它(由空格分隔),仍然能够查看第一列的层次结构!

我尝试了很多东西,但最终丢失了层次结构信息

【问题讨论】:

  • 不确定“在 Excel 中加载”是什么意思。您想对其进行格式化以便很好地粘贴到电子表格中吗?这是您问题的一部分,还是您只是在问如何用点替换前导空格?
  • @CAustin 很抱歉造成混乱。“excel”部分并不是真正的问题,只是我想要我想要的原因。
  • I tried in all, could not get it done...您应该在此处添加至少一个...否则问题将看起来像是免费询问代码...

标签: python regex vim awk sed


【解决方案1】:

以此为输入:

$ cat file
top           f1    f2    f3
   sub1       f1    f2    f3
   sub2       f1    f2    f3
      sub21   f1    f2    f3
   sub3       f1    f2    f3

试试:

$ sed -E ':a; s/^( *) ([^ ])/\1.\2/; ta' file
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

工作原理:

  • :a

    这会创建一个标签a

  • s/^( *) ([^ ])/\1.\2/

    如果行以空格开头,这会将前导空格中的最后一个空格替换为句点。

    更详细地说,^( *) 匹配除最后一个以外的所有前导空格并将它们存储在第 1 组中。正则表达式 ([^ ])(尽管 stackoverflow 看起来像这样,它由一个空格和 ([^ ]) 组成)匹配一个空白后跟一个非空白并将非空白存储在第 2 组中。

    \1.\2 将匹配的文本替换为第 1 组,后跟句点,然后是第 2 组。

  • ta

    如果替换命令导致替换,则分支返回标签 a 并重试。

兼容性:

  1. 以上是在现代 GNU sed 上测试的。对于 BSD/OSX sed,可能需要也可能不需要:

    sed -E -e :a -e 's/^( *) ([^ ])/\1.\2/' -e ta file
    

    在古老的 GNU sed 上,需要使用 -r 代替 -E

    sed -r ':a; s/^( *) ([^ ])/\1.\2/; ta' file
    
  2. 以上假设空格为空白。如果它们是制表符,那么您必须确定制表位是什么并相应地进行替换。

【讨论】:

  • 对于您的明确回答,我低下了头。感谢兼容性部分 - 原来我的工作机器使用“古代 GNU sed”并且需要“-r”。此外,我对“sed”在标签和“重试”能力方面的力量感到敬畏。太棒了,你也是!
  • @shikhanshu 不客气,谢谢你的客气话!
  • 它应该可以在没有第二个反向引用的情况下工作:sed -E ':a;s/^( *) /\1./g;ta;' file
【解决方案2】:

在 vim 中有两种不同的方法可以做到这一点。

  1. 使用正则表达式:

    :%s/^\s\+/\=repeat('.', len(submatch(0)))
    

    这相当简单,但有点冗长。它使用 eval 寄存器 (\=) 生成一个 '.'s 的字符串,其长度与每行开头的空格数相同。

  2. 使用规范命令:

    :%norm ^hviwr.
    

    这是一个更方便的简短命令,虽然有点难以理解。它直观地选择行首的空格,并用点替换整个选择。如果没有前导空格,则该命令将在 ^h 上失败,因为光标试图移出边界。

    要了解它是如何工作的,请尝试在带有前导空格的行上键入 ^hviwr. 以查看它发生的情况。

【讨论】:

  • regex 和 norm 命令都可以完美运行。我在没有 ^h 的情况下尝试了没有前导空格的行,我明白为什么需要 ^h :) 感谢这个解决方案!
【解决方案3】:

虽然有点冗长,但仍然是一个有趣的练习:

# Function to count the number of leading spaces in a string
# Basically, this counts the number of consecutive elements that satisfy being spaces
def count_leading_spaces(s):
    if not s:
        return 0
    else:
        curr_char = s[0]
        if curr_char != ' ':
            return 0
        else:
            idx = 1
            curr_char = s[idx]
            while curr_char == ' ':
                idx += 1
                try:
                    curr_char = s[idx]
                except IndexError:
                    return idx
        return idx

最后,打开文件并做一些工作:

with open('file.txt', 'r') as f:
    data = []
    for i, line in enumerate(f):
        # Don't do anything to the field names
        if i == 0:
            new_line = line.rstrip()
        else:
            n_leading_spaces = count_leading_spaces(line)
            # Impute periods for spaces
            new_line = ('.'*n_leading_spaces + line.lstrip()).rstrip()
        data.append(new_line)

结果:

>>> print('\n'.join(data))
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

你也可以这样做,更简单:

with open('file.txt', 'r') as f:
    data = []
    for i, line in enumerate(f):
        # Don't do anything to the field names
        if i == 0:
            new_line = line.rstrip()
        else:
            n_leading_spaces = len(line) - len(line.lstrip())
            # Impute periods for spaces
            new_line = line.lstrip().rjust(len(line), '.').rstrip()
        data.append(new_line)

【讨论】:

  • len(line) - len(line.lstrip()) 很聪明,我不知道为什么我没有想到它。感谢您的回答。
【解决方案4】:

既然你说python

#!/usr/bin/env python
import re, sys
for line in sys.stdin:
    sys.stdout.write(re.sub('^ +', lambda m: len(m.group(0)) * '.', line))

(对于每一行,我们将最长的前缀空格 '^ +' 替换为同样长的点串 len(m.group(0)) * '.')。

最终结果:

$ ./dottify.py <file
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

既然你说awk

$ awk '{ match($0,/^ +/); p=substr($0,0,RLENGTH); gsub(" ",".",p); print p""substr($0,RLENGTH+1) }' file
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

(对于每一行,我们将最长的空格前缀与match匹配,将其提取为substr,通过gsub将每个空格替换为点,并打印修改后的前缀p,然后是余数输入行的长度(RSTARTRLENGTH 变量填充在 match() 之后并保存匹配模式的起始位置和长度)。

【讨论】:

  • Python 1 太简洁了! 'awk' 一定需要认真思考,感谢您的精彩解释!我希望我可以在 SO 上选择多个答案
【解决方案5】:

在 awk 中。它不断用句号替换第一个空格,而空格之前只有句号:

$ awk '{while(/^\.* / && sub(/ /,"."));}1' file
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

这是 perl 中的一个:

$ perl -p -e 'while(s/(^\.*) /\1./){;}' file
top           f1    f2    f3
...sub1       f1    f2    f3
...sub2       f1    f2    f3
......sub21   f1    f2    f3
...sub3       f1    f2    f3

【讨论】:

  • 好吧,如果您使用 perl,请使用 e 功能... perl -pe 's/^ */"." x length($&amp;)/e'
  • 你和你对短线的追求 :P 那么你的可以缩短为 perl -pe 'while(s/^\.*\K /./){}' .... 但我的观点更多的是关于性能 ;)
  • 这是相当快的,顺便说一句。一开始有一百万条记录和 4 个空格,它开始对其他解决方案产生影响(好吧,我只测试了我的,sed 和其他 awk)。然后这又快了一点:gawk 'BEGIN{FS=OFS=""}{i=1;while($i==" ")$(i++)="."}1' file(在我有偏见的笔记本电脑上:)。
  • 你应该完全发布速度测试来回答:)
  • 巧妙的方法和优雅的awk 解决方案。不错!
猜你喜欢
  • 2019-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多