awkのインデントに従って、別の行に文字列を連結します。

awkのインデントに従って、別の行に文字列を連結します。

次のような複数行の文字列があります。

foo
foobar
  bar
  baz
    bat
bar

私は次のように終わりたいと思います:

foo
foobar.bar
foobar.baz
foobar.baz.bat
bar

私の考えは、各行に対して次の行をすべてチェックして、n次の文字列が特定の数の空白で始まることを確認し、空白の数に応じてそれに応じて文字列をフォーマットする必要があることです。

awkを使ってこれを達成できますか?

答え1

仮説牛に似た一種の栄養gawk...しかし、エラー訂正は含まれていません(あなたの言葉を信じなさい:「これはテキストファイルです」)...だから、:

$ cat file
foo
foobar
  bar
  baz
    bat
bar
$
$ gawk 'BEGIN {
    PROCINFO["sorted_in"] = "@ind_num_asc"
}

{
    match($0, /^[ \t]*/)
    if (RLENGTH == 0) {
        if (NR > 1 && length(a) == 1) {
            print a[0]
        }
        delete a
        a[0] = $0
        lsnum = RLENGTH
    }
    if (RLENGTH > lsnum) {
        lsnum = RLENGTH
        a[lsnum] = "." substr($0, RLENGTH + 1)
        p = 1
    }
    if (p == 1) {
        for (i in a) {
            printf "%s", a[i]
        }
        print ""
        lsnum = 0
        p = 0
    }
}

END {
    if (length(a) == 1) {
        print a[0]
    }
}' file
foo
foobar.bar
foobar.baz
foobar.baz.bat
bar

ファイルの行の前にスペースまたはタブがある場合はこれが機能します...しかし、2つの組み合わせが混在している場合は、タブをスペースに解析するか、その逆に解析するなど、いくつかの調整が必要になる場合があります。索引付けにより誤った配列要素が設定され、誤った出力が発生します。

答え2

そしてperl

<your-file expand | perl -lpe '
  ($indent, $txt) = /^( *)(.*)/;
  $depth = length($indent) / 2;
  $part[$depth] = $txt;
  $_ = join ".", @part[0..$depth]'

またはゴルフをする:

<your-file expand|perl -lpe'
  my$d;/^(  (?{$d++}))*/;$p[$d]=$'\'';$_=join".",@p[0..$d]'

(行の先頭に奇数の空白がある場合、テキストは空白文字で始めることもできます。)

expandタブが8列ごとに停止すると仮定して、タブ(存在する場合)を空白に拡張しますが、これはオプションで変更できます。

これに対応する内容はawk次のとおりです。

<your-file expand | awk '
  BEGIN {
    OFS = "."
    while ((getline line) > 0) {
      match(line, /^ */)
      $ (NF = RLENGTH / 2 + 1) = substr(line, RLENGTH + 1)
      print
    }
  }'

彼らは以下を提供します:

foo
foobar
foobar.bar
foobar.baz
foobar.baz.bat
bar

foobar予想される出力に欠けている行が間違いであるとします。

答え3

データが実際にnull各パスの値のみを含むYAML文書であるとしましょう。:質問データの行末に以下を追加するだけです。

$ sed 's/$/:/' file
foo:
foobar:
  bar:
  baz:
    bat:
bar:

これにより、ドキュメントで利用可能なすべてのパスをYAMLパーサーに要求できます。


そしてアンドレイ・キースリュークyq:

$ sed 's/$/:/' file | yq -r 'paths | join(".")'
foo
foobar
foobar.bar
foobar.baz
foobar.baz.bat
bar

pathsフィルタ(jqラッパーyq)は、指定された文書のすべてのパスを表す配列を出力します。このjoin()呼び出しは、これらのリストをドットで区切られた文字列に連結します。

値のあるパスのみを選択することで、中間パスのリストを避けることができますnullnot作成するより短い方法です. == null)。

$ sed 's/$/:/' file | yq -r 'paths(not) | join(".")'
foo
foobar.bar
foobar.baz.bat
bar

これにより、中間パスfoobarfoobar.baz


そしてマイクパラyq:

$ sed 's/$/:/' file | yq '.. | map(path | join(".")) | .[]'
foo
foobar
bar
foobar.bar
foobar.baz
foobar.baz.bat

Mikeの値を使用して文書の値を明示的に再帰し、yq値を取得するときと呼び出されるように準備する必要がありますpathjoin()

より短い式を使用してこれを実行することもできますが、..|path|join(".")出力に追加の空行が表示されます。

中間経路の出力を防止しますか?

$ sed 's/$/:/' file | yq '.. | map(select(not) | path | join(".")) | .[]'
foo
bar
foobar.bar
foobar.baz.bat

答え4

ここに解決策がありますTXR不明瞭な音声。アプローチは、プロジェクトでネストされたツリーを構築することです。その後、ドット表記で簡単に印刷できます(または興味深い方法で分析できます)。これは、拡張入力フ​​ァイルの処理に示されているように、突然あまりにも多くのレベルにインデントされたアイテムとインデントがinput2予期せずインデント解除された場合を処理します。

$ cat input2
foo
foobar
  bar
  baz
    bat
bar
xyzzy
      out1
      out2
stretched
      way
          out
weird
      indent
    deindent
$ txr code.tl < input2
foo
foobar
foobar.bar
foobar.baz
foobar.baz.bat
bar
xyzzy
xyzzy.out1
xyzzy.out2
stretched
stretched.way
stretched.way.out
weird
weird.indent
weird.deindent

コードへcode.tl

(defsymacro indent "  ")

(defun deindent (items)
  (mapcar (do if (starts-with indent @1)
            (drop (len indent) @1)
            @1)
          items))

(defun indent-to-tree (lines)
  (let ((pieces (partition-if (opip (starts-with indent @2) not) lines)))
    (append-matches (@(as piece (@head . @tail)) pieces)
      (cond
        ((starts-with indent head)
         (while* (starts-with indent (car piece))
           (upd piece deindent))
         (indent-to-tree piece))
        (t (list (cons head (indent-to-tree (deindent tail)))))))))

(defun dot-notation (items)
  (build
    (each ((item items))
      (tree-bind (node . children) item
        (add node)
        (each ((dnc (dot-notation children)))
          (add `@node.@dnc`))))))

(flow (get-lines) indent-to-tree dot-notation tprint)

最上位レベルでは、ワークフローはflowマクロで構成されます。標準入力は文字列リストに変換され、パイプを介してツリー構造が得られ、文字列リスト(get-lines)に変換され、標準出力に行にダンプされます。indent-to-treedot-notationtprint

ツリー変換は再帰的なプロセスです。各再帰レベルは、行リストをグループに分割し、インデントされていない各行が新しいグループを開始し始めます。

REPLでは、これがどのように見えるかを確認できます。

1> (flow (file-get-lines "input2")
         (partition-if (opip (starts-with indent @2) not)))
(("foo") ("foobar" "  bar" "  baz" "    bat") ("bar") ("xyzzy" "      out1" "      out2")
 ("stretched" "      way" "        out") ("weird" "      indent" "    deindent"))

関数indent-to-treeはこの分割を実行してからセクションを繰り返して、2つの主なケース、つまりインデントされたグループのみを処理するか、インデントされていない行で始まるインデントされたグループを処理します。このような場合は再帰的に処理されます。インデント専用グループでは、最初の行がインデントされなくなるまで繰り返しインデントをキャンセルしてから再度繰り返します。

indent-to-treeの行に適用すると、input2次のようになります。

2> (flow (file-get-lines "input2") indent-to-tree)
(("foo") ("foobar" ("bar") ("baz" ("bat"))) ("bar") ("xyzzy" ("out1") ("out2"))
 ("stretched" ("way" ("out"))) ("weird" ("indent") ("deindent")))

これがツリー表現です。ツリーの最上位レベルはアイテムのリストです。各項目はヘッダー文字列(コードから呼び出されますnode)で、その後にゼロ個以上の子があります。たとえば、サブキーのないヘッダー文字列があります("foo")"foo"そして子供たちの頭もあります("foobar" ....)。 2番目には子がありますが、それ自体に子はありません。"foobar"("bar")("baz" ("bat"))("bat")

この表記法は単純な再帰的過程を経て点表記法になり、これはほぼ全過程で脚注に過ぎない。プログラム作成リスト用の語彙環境を生成するマクロdot-notationに依存します。build内部的にを使用してリストに項目を追加buildできます。 2回発生します。文字列がサブアイテムなしで一覧表示されるようにヘッダー文字列を追加し、各サブアイテムに対して再リストし、そのサブアイテムのドット表記を追加します。(add ...)addfoobar

メモ:

  • indent(1つのスペース)として定義すると、すべてがまだ機能するため、インデントが" "2つのスペースの倍数と整列しないなど、より多くのケースを処理するため、そうする必要があります。

  • 空の行を絞るのは良い考えです。これはまだ完了しておらず、正しく機能しません。

  • の述語関数partition-ifは引数が2つのプライベート関数なので、構文はop引数を参照します@2partition-if連続して重複する要素のペアを使用してこの関数を呼び出します。関数がtrueを返すと、これらの要素間のシーケンスが分割されます。例えば

    ;; start new partition whenever an element is smaller
    ;; than its predecessor.
    (partition-if (op > @1 @2) '(1 2 3 4 3 2 1 0 1 2 3))
    --> ((1 2 3 4) (3) (2) (1) (0 1 2 3))
    
  • while*whileループボトムテストを実行するバリアントです。もう1つの観点では、最初の繰り返しの前にループガードをスキップし、無条件に本文を実行してからのように動作しますwhile

関連情報