다음과 같은 여러 줄 문자열이 있습니다.
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
이렇게 하면 중간 경로 foobar
와 foobar.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 ...)
add
foobar
노트:
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
.