Bash

如果父目錄也在列表中,則從列表中刪除路徑

  • April 21, 2021

我的標題可能有點奇怪,所以這是我的情況:我有一堆目錄路徑,例如

/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,因此它們應該可靠地按自上而下的順序排列。解析為數組或多行字元串的解決方案都受到歡迎。

我假設路徑名列表可能未排序,並且生成的路徑名列表應該與輸入中的順序相同。我還假設沒有路徑名包含嵌入的換行符。

使用/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)。如果沒有發現接受的路徑名是目前路徑名的目錄路徑前綴,則接受目前路徑名。

接受的路徑名列表保存在位置參數中。

bashshell 顯然可以執行上面的腳本,但是如果你想要專門為那個 shell 編寫的東西,你可以說

#!/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程序與 shell 程式碼變體之間的明顯相似之處。

這用於index()測試接受的路徑名是否是目前路徑名的前綴。您可以if ($0 ~ "^" acceped[i] "/")改用,但這樣做的缺點是路徑名本身用作正則表達式的一部分。一旦您的路徑名包含諸如等字元,這就會變得很.重要*

如果我沒記錯的話,一個標準化(*)的列表,或者至少是一致呈現的路徑,按照通常的詞典排序,有一個目錄的子目錄立即出現在該目錄之後(遞歸地)。因此,僅查看前一行(未刪除)就足夠了。

(* 通過規範化,我的意思是/foo/baror /foo/bar/,而不是例如 /foo/asdf/../baror /foo///bar//。輸出find不會有問題,因為如果給定一個非規範化的起始目錄,它確實會給出非規範化的輸出,但輸出至少是一致的。)

一條路徑仍然可以是另一個路徑的前綴,但它只是兄弟而不是父路徑,例如/fooand /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(...)以保留它們。)

引用自:https://unix.stackexchange.com/questions/645966