sed:在同一行下方设置以下多行

时间:2019-04-14 18:56:44

标签: awk sed

我有一个如下的文本文件

# jkakjshkjh
  *   drink  (2 spaces *  2 spaces)(non hash starting)
 *   biscuit  (1 space * 2 spaces)(non hash starting)
* paper       (* 1 space)(non has starting)
... (many more lines) of non hash starting
     *  tea   (7 spaces * 3 space)(non has starting)
# happy
* cup       (* 1 space)(non has starting)
  *   bat  (2 spaces *  2 spaces)(non hash starting)
 *   scooter  (1 space * 2 spaces)(non hash starting)
... (many more lines) of non hash starting
     *  disk   (7 spaces * 3 space)(non has starting)

我希望所有非哈希起始行都具有与第一条非哈希起始行相同的开始

即:

# jkakjshkjh
  *   drink  (2 spaces *  2 spaces)(non hash starting)
  *   biscuit  (2 spaces *  2 spaces)(non hash starting)
  *   paper  (2 spaces *  2 spaces)(non hash starting)
   ... (many more lines of non hash starting)
  *   tea  (2 spaces *  2 spaces)(non hash starting)
# happy
* cup       (* 1 space)(non has starting)
* bat       (* 1 space)(non has starting)
* scooter       (* 1 space)(non has starting)
... (many more lines) of non hash starting
* disk       (* 1 space)(non has starting)

现在,以上问题有所不同。

1)第一个非哈希行并不总是以(2个空格* 2个空格)开头

它可以变化(1个空格* 1个空格)或(前置空格的rad数量*帖子空格的随机数量)

2)在中间,如果有一行以哈希开头,则不应触摸该行

那么如何用sed解决以上问题

我尝试了以下方法:

sed -Ez 's/(\n)([^#]\s+\*\s+)([^\n]*\n)([^#]\s+\*\s+)([^\n]*\n)/\1\2\3\2\5/g' filename

上面只会检查两条结果行。这样做的问题是将2行视为一个单元。因此,两行的组将具有相同的开始。但我希望所有人都具有与第一条非哈希起跑线相同的开始

4 个答案:

答案 0 :(得分:2)

如果awk也是一个选项,那么这里是一个可移植的选项:

awk 'match($0,/^ *\* */){
  if(b) $0=b substr($0,RLENGTH+1)
  else b=substr($0,1,RLENGTH)
} /^#/{b=""} 1' file

说明

  • b代表开始,即您在问题中使用它的含义(n个空间* n个空间),
  • match($0, /^ *\* */)/^ *\* */几乎相同,它匹配零个或多个空格,后跟一个*,然后在$0的开头匹配零个或多个空格(即当前行)。如果存在匹配项,RSTARTRLENGTH分别设置为匹配的子字符串开始的位置及其长度,
    • if (b)if (b != "")的简写,并且在这里可以安全使用,因为在这种情况下b的值不能为0,
    • $0 = b substr($0, RLENGTH + 1)b替换当前行的开头
    • b = substr($0, 1, RLENGTH)b设置为当前行的开始
  • /^#/ { b = "" }表示当前行是否以#重置b开始,
  • 1表示打印$0

答案 1 :(得分:1)

如果您可以采用非sed解决方案,则可以:使用GNU awk将第三个参数匹配():

$ cat tst.awk
{
    match($0,/^(\s*(\S)\s*)(.*)/,a)
    currHead = a[1]
    currChar = a[2]
    currTail = a[3]
}
currChar == "#" { indent = currHead }
currChar != "#" { indent = (prevChar == "#" ? currHead : indent) }
{ printf "%s%s\n", indent, currTail; prevChar = currChar }

$ awk -f tst.awk file
# jkakjshkjh
  *   drink  (2 spaces *  2 spaces)(non hash starting)
  *   biscuit  (1 space * 2 spaces)(non hash starting)
  *   paper       (* 1 space)(non has starting)
  *   .. (many more lines) of non hash starting
  *   tea   (7 spaces * 3 space)(non has starting)
# happy
* cup       (* 1 space)(non has starting)
* bat  (2 spaces *  2 spaces)(non hash starting)
* scooter  (1 space * 2 spaces)(non hash starting)
* .. (many more lines) of non hash starting
* disk   (7 spaces * 3 space)(non has starting)

通过其他操作,您只需使用substr()来获取match()放入a[]中的部分,并使用[[:space:]][^[:space:]]分别用于\s\S

为帮助您理解语法,如果我是使用类似C的语言编写以上内容,那么它将是:

while ( read(FILENAME,line) ) {                 # awk does this for you
    NR++;                                       # awk does this for you
    NF = split(line into $1, $2, $3, ... $NF);  # awk does this for you
    match(line,/^(\s*(\S)\s*)(.*)/,a);
    currHead = a[1];
    currChar = a[2];
    currTail = a[3];
    if (currChar == "#") { indent = currHead; }
    if (currChar != "#") { indent = (prevChar == "#" ? currHead : indent); }
    printf "%s%s\n", indent, currTail; prevChar = currChar;
}                                               # awk does this for you

实际上,您可以在awks BEGIN部分中使用以下方式复制该语法:

BEGIN {
    filename = ARGV[1]
    ARGV[1] = ""
    ARGC--
    while ( (getline line < filename) > 0) ) {
        nr++
        nf = split(line,flds)
        match(line,/^(\s*(\S)\s*)(.*)/,a)
        currHead = a[1]
        currChar = a[2]
        currTail = a[3]
        if (currChar == "#") { indent = currHead }
        if (currChar != "#") { indent = (prevChar == "#" ? currHead : indent) }
        printf "%s%s\n", indent, currTail; prevChar = currChar
    }
}

但是请参见http://awk.freeshell.org/AllAboutGetline,除非您有非常特殊的需求,否则为什么不这样做。

答案 2 :(得分:1)

我认为这可以做到:

/

翻译:
如果该行以“#”开头,​​则也请阅读下一行,同时打印它们,然后删除第一行以及空格和星星串之后的所有内容,然后将剩下的内容(即空格和星星串)放入保持空间并结束。
否则,请删除前导空格和星号,附加保留空间的内容(即所需的空格和星号字符串),然后交换两部分(从而将所需的前缀放在前面)。

答案 3 :(得分:0)

如果您的“ d”文件中的数据尝试gnu sed,

sed -E ':b /#/{n; p;s/(\s*\*\s*).+/\1/;h;Tb;:l $!N;s/(.*)\n[* ]+(.+)/\1\2/;Tn;p;g;bl;:n D}' d