2

入力コードにいくつかの構造定義があります。例えば:

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

また

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

次の行を使用して、それらを 1 行に変換し、2 回コピーしました。

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. 2 行目を次のような関数見出しに変更したいと思います...

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

-structName は基本的に構造体の名前です。

-var は任意の名前です。

-data1、data2.... は、構造体にある値です。

3.3行目を関数本体に変えてほしい。データパラメータを初期化する場所。このようになります。

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

入力ファイル内のすべての構造体定義が 1 行に配置され、3 回コピーされることに注意してください。したがって、コードが構造定義を見つけると、その下にさらに 2 つのコピーがあると想定できます。

たとえば、入力ファイルに上記の繰り返し行がある場合、これが必要な出力です。

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

私が行ったことをすべて説明しようと思いますが、最初に、これはおそらくあまり一般化されていないことを警告しなければなりません。たとえば、3 本の同一の線が互いに続く (つまり、その間に他の線がない) と仮定します。

開始する前に、ファイルが「-n」フラグを実行する必要があるスクリプトであることに注意してください。これは、スクリプトが明示的に指示しない限り(たとえば、「p」コマンドを使用して)、標準出力に何も出力しないようにsedに指示します。「-f」オプションは、次のファイルを開くようにsedに指示する「トリック」です。「./myscript.sed」でスクリプトを実行すると、bash は「/bin/sed -nf myscript.sed」を実行するため、残りのスクリプトを正しく読み取ります。

ステップ 0 は、有効な行があるかどうかを確認するだけのチェックです。すべての有効な行に struct という単語が含まれていると想定しています。行が有効な場合、スクリプトは分岐します (ジャンプ、「b」コマンドは C の goto ステートメントに相当します) 継続ラベル (C とは異なり、ラベルは「:」で終わるのではなく、「:」で始まります)。有効でない場合は、"p" コマンドで強制的に出力し、"d" コマンドでパターン スペースからその行を削除します。行を削除することで、sed は次の行を読み取り、最初からスクリプトの実行を開始します。

回線が有効な場合、回線を変更するアクションが開始されます。最初のステップは、構造体本体を生成することです。これは、一連のコマンドによって実行されます。

  1. 行を 3 つの部分に分けます。開き括弧までのすべて、閉じ括弧までのすべて (ただし、閉じ括弧は含めません)、および閉じ括弧からのすべて (現在は含めます)。sed の癖の 1 つは、"\n" で改行を検索することですが、"\" の後に実際の改行が続く改行を記述します。そのため、このコマンドは 3 つの異なる行に分割されています。IIRC この動作は POSIX sed に固有のものですが、おそらく GNU バージョン (ほとんどの Linux ディストリビューションに存在) では、"\n" で改行を書くことができます。
  2. すべてのセミコロンの後に改行を追加します。この動作は少しぎこちなく、セミコロンの後に挿入された改行の後、セミコロンの後のすべてをコピーします。g フラグは sed にこれを繰り返し行うように指示するため、機能します。また、改行エスケープにも注意してください。
  3. 結果を強制的に印刷する

2 番目のステップの前に、パターン スペース (つまり、バッファー) から行を手動でクリアして、次の行を新たに開始できるようにします。「d」コマンドでこれを行った場合、sed はファイルの先頭からコマンドの読み取りを再度開始します。「n」コマンドは、次の行をパターン空間に読み込みます。その後、コマンドを開始して、行を関数宣言に変換します。

  1. 最初に struct という単語に一致し、その後に 0 個以上の空白が続き、次にアンダースコアまたはアルファベット文字で始まり、アンダースコアと英数字を含むことができる C 識別子が続きます。識別子は「変数」「\1」に取り込まれます。次に、「\2」に格納されている括弧内のコンテンツを一致させます。これらは、関数宣言を生成するために使用されます。
  2. 次に、同じプロセスを実行しますが、今度は「typedef」の場合です。識別子が括弧の後にあることに注意してください。つまり、"\1" には括弧内の内容が含まれ、"\2" には識別子が含まれます。
  3. ここで、すべてのセミコロンをカンマに置き換えて、関数定義のように見えるようにします。
  4. 最後の置換コマンドは、閉じ括弧の前にある余分なコンマを削除します。
  5. 最後に結果を印刷します。

繰り返しますが、最後のステップの前に、手動でパターン スペースを消去し、次の行を読み取ります。次に、ステップは関数本体を生成します。

  1. 括弧内のすべてを一致させてキャプチャします。開き括弧の前と閉じ括弧の後の ".*" に注意してください。これは、ブラケットの内容のみが後で書き込まれるように使用されます。出力を書くとき、ブラケットの開始を別の行に配置します。
  2. アルファベットとスペースを一致させるので、型宣言をスキップできます。識別子の開始を示すために、少なくとも空白文字またはアスタリスク (ポインタ用) が必要です。次に、識別子の取得に進みます。これは、キャプチャに続くものが原因でのみ機能します。識別子の後には、オプションの空白のみがあり、その後にセミコロンが続くことを明示的に要求します。これにより、セミコロンの前に識別子文字を取得する式が強制されます。3 つ以上の単語がある場合は、最後の単語のみが取得されます。したがって、「unsigned int var」で動作し、「var」を正しくキャプチャします。出力を書き込むとき、インデントを配置し、その後にエスケープされた改行を含む目的の形式を続けます。
  3. 最終出力を印刷します。

私が十分に明確だったかどうかはわかりません。ご不明な点がございましたら、お気軽にお問い合わせください。

これが役立つことを願っています=)

于 2012-09-25T00:58:56.637 に答える
1

これにより、この種のタスクに sed が実際にどれほど不適切であるかについて、いくつかのヒントが得られるはずです。1 回のパスでそれを行う方法を理解できず、スクリプトを書き終えるまでに、多少異なる結果を期待していることに気付きました。

あなたの問題は、スクリプト言語と解析ライブラリに適しています。python + pyparsing (これは C 構造体の構文解析の例ですが、それよりもはるかに単純なものが必要です) または perl6 のrulesを検討してください。

それでも、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 に答える