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

그리고 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()호출은 이러한 목록을 점으로 구분된 문자열로 연결합니다.

값이 있는 경로만 선택하여 중간 경로 나열을 피할 수 있습니다 null( not작성하는 더 짧은 방법입니다 . == 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값을 가져올 때 및 가 호출되도록 준비 해야 합니다 path.join()

더 짧은 표현식을 사용하여 이 작업을 수행할 수도 있지만 ..|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-tree트리 구조를 얻은 다음 dot-notation문자열 목록으로 변환된 다음 표준 출력에 줄로 덤프됩니다 tprint.

트리 변환은 재귀적인 프로세스입니다. 각 재귀 수준은 줄 목록을 그룹으로 나누어 들여쓰기되지 않은 각 줄이 새 그룹을 시작하도록 시작합니다.

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는 이 분할을 수행한 다음 섹션을 반복하여 두 가지 주요 사례, 즉 들여쓰기된 그룹만 처리하거나 들여쓰기되지 않은 줄로 시작하는 들여쓰기된 그룹을 처리합니다. 이러한 경우는 재귀적으로 처리됩니다. 들여쓰기 전용 그룹에서는 첫 번째 줄이 더 이상 들여쓰기되지 않을 때까지 반복적으로 들여쓰기를 취소한 다음 다시 반복합니다.

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)이고 그 뒤에는 0개 이상의 하위 항목이 있습니다. 예를 들어 하위 키가 없는 헤더 문자열이 있습니다 ("foo"). "foo"그리고 아이들의 머리도 있고 ("foobar" ....). 두 번째에는 자식이 있지만 그 자체에는 자식이 없습니다."foobar"("bar")("baz" ("bat"))("bat")

이 표기법은 단순한 재귀적 과정을 거쳐 점 표기법이 되었는데, 이는 거의 전체 과정에서 각주에 불과합니다. 프로그램 작성 목록을 위한 어휘 환경을 생성하는 매크로 dot-notation에 의존합니다 . build내부적으로 를 사용하여 목록에 항목을 추가 build할 수 있습니다 . 두 번 발생합니다. 문자열이 하위 항목 없이 나열되도록 헤더 문자열을 추가한 다음 각 하위 항목에 대해 다시 나열하고 해당 하위 항목의 점 표기법을 추가합니다.(add ...)addfoobar

노트:

  • indent(하나의 공백)으로 정의하면 모든 것이 여전히 작동하므로 들여쓰기가 " "두 공백의 배수와 정렬되지 않는 것과 같은 더 많은 경우를 처리하므로 그렇게 해야 합니다.

  • 빈 줄을 짜내는 것은 좋은 생각입니다. 이는 아직 완료되지 않았으며 제대로 작동하지 않습니다.

  • 의 조건자 함수 partition-if는 인수가 두 개인 함수이므로 op구문이 인수를 참조합니다 @2. partition-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루프 하단 테스트를 수행하는 변형입니다. 또 다른 관점에서는 첫 번째 반복 전에 루프 가드를 건너뛰고 무조건 본문을 실행한 다음 처럼 동작한다는 것입니다 while.

관련 정보