내 제목 표현이 약간 이상할 수 있으므로 내 상황은 다음과 같습니다.
/a/b
/a/b/c
/a/b/c/d
/a/e/f/g/h
/a/e/f/g/h/i/j/k/l
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
목록에 이미 존재하는 항목의 하위 경로인 모든 줄을 필터링하고 싶습니다.
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
디렉토리 경로는 에서 가져오므 find
로 하향식 순서로 안정적으로 정렬되어야 합니다. 배열이나 여러 줄 문자열로 구문 분석하는 솔루션은 모두 환영합니다.
답변1
나는 경로 이름 목록이 정렬되지 않을 수 있고 결과 경로 이름 목록이 입력과 동일한 순서여야 한다고 가정합니다. 또한 경로 이름에 개행 문자가 포함되어 있지 않다고 가정합니다.
사용 /bin/sh
:
#!/bin/sh
set --
while IFS= read -r pathname; do
for p do
case $pathname in ("$p"/*) continue 2 ;; esac
done
set -- "$@" "$pathname"
done <list
printf '%s\n' "$@"
그러면 파일에서 list
한 번에 한 줄씩 경로 이름을 읽습니다. 허용된 경로 이름(처음에는 빈 목록)은 내부 루프에서 한 번에 하나씩 읽은 각 경로 이름에 대해 테스트됩니다. 허용된 경로 이름이 현재 경로 이름의 디렉토리 경로 접두어인 경우 현재 경로 이름은 삭제됩니다(내부 루프는 외부 루프의 다음 반복으로 점프를 사용합니다 continue 2
). 현재 경로 이름인 경로 이름을 허용하는 디렉터리 경로 접두어가 발견되지 않으면 현재 경로 이름이 허용됩니다.
허용되는 경로 이름 목록은 위치 매개변수에 보관됩니다.
쉘은 bash
분명히 위의 스크립트를 실행할 수 있지만 해당 쉘을 위해 특별히 작성된 것을 원한다면 다음과 같이 말할 수 있습니다.
#!/bin/bash
accepted=()
while IFS= read -r pathname; do
for p in "${accepted[@]}"; do
[[ $pathname == "$p"/* ]] && continue 2
done
accepted+=("$pathname")
done <list
printf '%s\n' "${accepted[@]}"
awk
위와 동일한 방법을 사용하십시오 .
$ awk '{ for (i=1; i<=n; ++i) if (index($0, accepted[i] "/") == 1) next; accepted[++n]=$0 } END { for (i=1; i<=n; ++i) print accepted[i] }' list
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
코드가 awk
개선되었습니다.
{
for (i = 1; i <= n; ++i)
if (index($0, accepted[i] "/") == 1)
next
accepted[++n] = $0
}
END {
for (i = 1; i <= n; ++i)
print accepted[i]
}
awk
이 프로그램과 쉘 코드 변형 사이의 명백한 유사점을 처음부터 바로 볼 수 있어야 합니다 .
index()
이는 허용된 경로 이름이 현재 경로 이름의 접두사인지 테스트 하는 데 사용됩니다 . 이것을 사용할 수도 있지만 if ($0 ~ "^" acceped[i] "/")
이 방법의 단점은 경로 이름 자체가 정규식의 일부로 사용된다는 것입니다. 경로 이름에 와 같은 문자가 포함되면 이는 .
중요해 집니다 *
.
답변2
내가 정확하게 기억한다면, 정규화된(*) 목록 또는 적어도 일관성 있게 렌더링된 경로는 일반적인 사전순으로 정렬되며, 디렉터리의 하위 디렉터리는 해당 디렉터리 바로 뒤에(재귀적으로) 나타납니다. 따라서 이전 행(삭제되지 않은 행)만 살펴보는 것으로 충분합니다.
(* 정규화란 /foo/bar
or 를 의미합니다. /foo/bar/
예를 들어
/foo/asdf/../bar
or /foo///bar//
. 의 출력 find
은 정규화되지 않은 시작 디렉토리가 주어지면 정규화되지 않은 출력을 제공하지만 출력은 적어도 일관성이 있기 때문에 문제가 되지 않습니다.)
/foo
경로는 및 와 같이 부모가 아닌 형제 경로인 동시에 다른 경로의 접두사가 될 수 있습니다 /foobar
. 이 상황을 처리하기 위해 아직 슬래시가 없는 경우 각 줄에 후행 슬래시를 추가할 수 있습니다.
따라서 ( 테스트에 /foo
및를 추가하고 코드를 작성하려고 하지 않음):/foobar
$ sort paths.txt | awk '! /\/$/ { $0 = $0 "/" }
last && last == substr($0, 1, length(last)) { next; }
{ last = $0; sub(/\/$/, "", $0); print }'
/a/b
/a/e/f/g/h
/a/e/f/g/m/n/o
/a/e/f/g/m/n/p
/foo
/foobar
$0
첫 번째 줄은 필요한 경우 현재 줄에 슬래시를 추가합니다. 두 번째 줄은 마지막으로 저장된 줄(있는 last
경우)과 비교하고 세 번째 줄은 삭제되지 않은 모든 줄을 저장하고 인쇄합니다. 슬래시가 제거됩니다. ( sub(...)
보존하려면 삭제하세요.)
답변3
짧은앗해결책:
<infile sort -u |awk 'NR==1 || index($0, pre"/")!=1{print; pre=$0}'
답변4
GNU sed확장된 정규식 패턴을 사용하십시오 -E
. 하위 집합이 없는 이전 행은 예약된 공간에 저장됩니다.
< file sort \
| sed -En '
G
/^([^\n]+)\/.*\n\1$/d
s/\n.*//p;h
'
< file sort \
| perl -lne '
$prev //= $_;
print($prev = $_)
if index($_, "$prev/");
'
POSIX sed 허용되지 않으므로 [^\n]
POSIX 호환 구조를 사용하여 다시 작성합니다.
< file sort \
| sed -e '
H;x
\|^\(..*\)\n\1/|{
s/\n.*//;h;d
}
g
'