2

我的输入代码中有几个结构定义。例如:

struct node {
   int val;
   struct node *next;
};

或者

typedef struct {
   int numer;
   int denom;
} Rational;

我使用以下行将它们转换为一行并复制两次。

sed '/struct[^(){]*{/{:l N;s/\n//;/}[^}]*;/!t l;s/  */ /g;p;p}'

结果是这样的:

struct node { int val; struct node *next;};
struct node { int val; struct node *next;};
struct node { int val; struct node *next;};

typedef struct { int numer; int denom;} Rational;
typedef struct { int numer; int denom;} Rational;
typedef struct { int numer; int denom;} Rational;

这就是我要的:

  1. 我想将第一行恢复到原来的结构块
  2. 我希望第二行变成一个看起来像这样的函数标题......

    void init_structName( structName *var, int data1, int data2 ) 
    

-structName 基本上是结构的名称。

-var 是您喜欢的任何名称。

-data1, data2.... 是结构中的值。

3.我想把第三行变成函数体。我在哪里初始化数据参数。它看起来像这样。

    {
        var->data1 = data1;
        var->data2 = data2;
    }

请记住,我在输入文件中的所有结构定义都放在一行中并复制了三遍。因此,当代码找到结构定义时,它可以假设下面还会有两个副本。

例如,如果输入文件具有上面显示的重复行,这就是我想要的输出。

struct node {
   int val;
   struct node *next;
};
void init_node(struct node *var, int val, struct node *next)
{
var->val = val;
var->next =  next;
}

typedef struct {
   int numer;
   int denom;
} Rational;
void init_Rational( Rational *var, int numer, int denom ) 
{
   var->numer = numer;
   var->denom = denom;
}

以防有人好奇。这些函数将从主函数调用以初始化结构变量。

有人可以帮忙吗?我意识到这有点困难。非常感谢!!

4

2 回答 2

4

看到sed 是 Turing Complete,可以一次性完成,但这并不意味着代码非常用户友好 =)

我对解决方案的尝试是:

#!/bin/sed -nf

/struct/b continue
p
d

: continue

# 1st step:
s/\(struct\s.*{\)\([^}]*\)\(}.*\)/\1\
\2\
\3/
s/;\(\s*[^\n}]\)/;\
\1/g
p

s/.*//
n
# 2nd step:
s/struct\s*\([A-Za-z_][A-Za-z_0-9]*\)\s*{\([^}]*\)}.*/void init_\1(struct \1 *var, \2)/
s/typedef\s*struct\s*{\([^}]*\)}\s*\([A-Za-z_][A-Za-z_0-9]*\)\s*;/void init_\2(struct \2 *var, \1)/
s/;/,/g
s/,\s*)/)/
p

s/.*//
n
# 3rd step
s/.*{\s*\([^}]*\)}.*/{\
\1}/
s/[A-Za-z \t]*[\* \t]\s*\([A-Za-z_][A-Za-z_0-9]*\)\s*;/\tvar->\1 = \1;\
/g
p

我将尝试解释我所做的一切,但首先我必须警告这可能不是很笼统。例如,它假设三个相同的线彼此跟随(即它们之间没有其他线)。

在开始之前,请注意该文件是一个需要“-n”标志才能运行的脚本。这告诉 sed 除非脚本明确告诉它(例如通过“p”命令),否则不要将任何内容打印到标准输出。“-f”选项是告诉 sed 打开后面的文件的“技巧”。当使用“./myscript.sed”执行脚本时,bash 将执行“/bin/sed -nf myscript.sed”,因此它将正确读取脚本的其余部分。

步骤零只是检查我们是否有一条有效的线。我假设每个有效行都包含单词 struct。如果该行有效,则脚本分支(跳转,“b”命令相当于 C 中的 goto 语句)到 continue 标签(与 C 不同,标签以“:”开头,而不是以它结尾)。如果它无效,我们用“p”命令强制打印它,然后用“d”命令从模式空间中删除该行。通过删除该行,sed 将读取下一行并从头开始执行脚本。

如果该行有效,则更改该行的操作开始。第一步是生成结构体。这是通过一系列命令完成的。

  1. 将该行分成三部分,从左括号开始的所有内容,直到右括号的所有内容(但不包括它),以及来自右括号的所有内容(现在包括它)。我应该提到,sed 的一个怪癖是我们用“\n”搜索换行符,但是用“\”写换行符,然后是一个实际的换行符。这就是为什么这个命令被分成三个不同的行。IIRC 这种行为特定于 POSIX sed,但可能 GNU 版本(存在于大多数 Linux 发行版中)允许使用“\n”编写换行符。
  2. 在每个分号后添加一个换行符。这个工作有点尴尬,我们在分号后插入换行符后复制分号后的所有内容。g 标志告诉 sed 重复执行此操作,这就是它起作用的原因。还要再次注意换行符转义。
  3. 强制打印结果

在第二步之前,我们手动从模式空间(即缓冲区)中清除行,以便我们可以重新开始下一行。如果我们使用“d”命令执行此操作,sed 将再次从文件开头开始读取命令。然后“n”命令将下一行读入模式空间。之后,我们启动命令将该行转换为函数声明:

  1. 我们首先匹配单词 struct,后跟零个或多个空格,然后是 C 标识符,该标识符可以以下划线或字母开头,并且可以包含下划线和字母数字字符。标识符被捕获到“变量”“\1”中。然后我们匹配括号之间的内容,存储到“\2”中。然后这些用于生成函数声明。
  2. 然后我们执行相同的过程,但现在是针对“typedef”的情况。请注意,现在标识符位于括号之后,因此“\1”现在包含括号内的内容,“\2”包含标识符。
  3. 现在我们用逗号替换所有分号,所以它可以开始看起来更像一个函数定义。
  4. 最后一个替换命令删除右括号之前的多余逗号。
  5. 最后打印结果。

同样,在最后一步之前,手动清理模式空间并阅读下一行。然后该步骤将生成函数体:

  1. 匹配并捕获括号内的所有内容。注意左括号之前和右括号之后的“.*”。使用它,因此只有括号的内容在之后被写入。在编写输出时,我们将左括号放在单独的行中。
  2. 我们匹配字母字符和空格,因此我们可以跳过类型声明。我们至少需要一个空格字符或一个星号(用于指针)来标记标识符的开始。然后我们继续捕获标识符。这只适用于捕获之后的内容:我们明确要求在标识符之后只有可选的空格,后跟分号。这会强制表达式在分号之前获取标识符字符,即。如果有两个以上的单词,它只会得到最后一个单词。因此它可以与“unsigned int var”一起使用,正确捕获“var”。在编写输出时,我们放置一些缩进,然后是所需的格式,包括转义的换行符。
  3. 打印最终输出。

我不知道我是否足够清楚。随时要求任何澄清。

希望这会有所帮助=)

于 2012-09-25T00:58:56.637 回答
1

这应该为您提供一些关于 sed 实际上对于此类任务有多不合适的提示。我无法弄清楚如何一次性完成,当我完成编写脚本时,我注意到您期望的结果有些不同。

您的问题更适合脚本语言和解析库。考虑 python + pyparsing(这里是一个示例 C 结构解析语法,但您需要比这更简单的东西)或 perl6 的规则

不过,如果您决定坚持使用 sed,也许这会有一些用处:

pass-one.sh

#!/bin/sed -nf

/^struct/ {
  s|^\(struct[^(){]*{\)|\1\n|
  s|[^}];|;\n|gp
  a \\n
}

/^typedef/ {
  h
  # create signature
  s|.*{\(.*\)} \(.*\);|void init_\2( \2 *var, \1 ) {|
  # insert argument list to signature and remove trailing ;
  s|\([^;]*\); ) {|\1 ) {|g
  s|;|,|g
  p

  g
  # add constructor (further substitutions follow in pass-two)
  s|.*{\(.*\)}.*|\1|
  s|;|;\n|g
  s|\n$||p

  a }
  a \\n
}

pass-two.sh

#!/bin/sed -f

# fix struct indent
/^struct/ {
  :loop1
  n
  s|^ |    |
  t loop1
}

# unsigned int name -> var->name = name
/^void init_/{
  :loop2
  n
  s|.* \(.*\);|    var->\1 = \1;|
  t loop2
}

Usage

$ cat << EOF | ./pass-one.sh | ./pass-two.sh
struct node { int val; struct node *next;};
typedef struct { int numer; int denom;} Rational;

struct node { int val; struct node *next;};
typedef struct { int numer; unsigned int denom;} Rational;
EOF
struct node {
    int va;
    struct node *nex;
};


void init_Rational( Rational *var,  int numer, int denom ) {
    var->numer = numer;
    var->denom = denom;
}


struct node {
    int va;
    struct node *nex;
};


void init_Rational( Rational *var,  int numer, unsigned int denom ) {
    var->numer = numer;
    var->denom = denom;
}
于 2012-09-24T12:46:27.807 回答