我需要一種方法來搜尋目錄中具有相同名稱的子目錄,然後將子目錄中的所有檔案移至父目錄。因此,
/recup-dir1/recup-dir1/files to /recup-dir1/files.
子目錄可以留空,因為我可以使用類似
find . -type -d -empty -delete
刪除所有空目錄的方法
所以問題是我不知道哪些目錄有同名的子目錄,哪些目錄沒有。
在偽代碼中我需要這樣的東西。
While more directories are unchecked
get name-x of next dir
enter dir
If name-x/name-x exist
move all files in name-x/name-x to name-x
mark dir as done
next
我最好的猜測是創建一個小 python 腳本來創建一個包含同名子目錄的所有目錄的列表,並通過以下命令循環此列表
find something something -exec mv
也許這可以透過 bash 腳本來完成,或者有其他解決方案。就像一些 rsync 命令一樣,但由於我可能使用 rsync 創建了這個混亂,我認為這不是解決方案。
編輯:這是樹輸出的實際部分:頂級目錄位於 /mnt/external-disk/tst-backup 較低層級上沒有子目錄。
│ └── recup_dir.1
├── recup_dir.10
│ └── recup_dir.10
├── recup_dir.100
│ └── recup_dir.100
├── recup_dir.102
│ └── recup_dir.102
└── recup_dir.1020
└── recup_dir.1020
答案1
有了zsh
,你可以這樣做:
for dir in **/*(NDodoN/e['[[ $REPLY:t = $REPLY:h:t ]]']); do
contents=($dir/*(NDoN))
(( $#contents == 0 )) ||
mv -- $contents $dir:h/ &&
rmdir -- $dir
done
在哪裡:
**/*(qualifiers)
使用 glob 限定符進行遞歸 globbingN
: nullglob: 如果沒有匹配,不要抱怨D
: dotglob: 包含隱藏文件od
:順序深度優先(葉子在它們所在的分支之前)。oN
:否則不必費心排序文件清單。/
:限制為目錄類型的檔案。e['expression']
:限製expression
程式碼傳回 true 的檔案(目前檔案路徑儲存在其中$REPLY
)。$REPLY:t
: 文件的尾部(基本名稱)$REPLY:h:t
:檔案頭(目錄名)的尾部)
對於bash
4.4+ 和 GNUfind
或find
大多數 BSD,您可以執行類似的操作:
shopt -s nullglob dotglob
readarray -td '' dirs < <(
LC_ALL=C find . -depth -regex '.*\(/[^/]*\)\1' -type d -print0
)
for d in "${dirs[@]}"; do
contents=("$d"/*)
(( ${#contents[@]} == 0 )) ||
mv -- "${contents[@]}" "${d%/*}/" &&
rmdir -- "$d"
done
這次使用正規表示式來符合./path/to/dir/dir
使用基本正規表示式反向引用的檔案。
答案2
試試一下,基於 GNU find
v4.8.0 和 Bash v5.1.8
第 1 部分:解析目錄樹 + 偵測子目錄名稱重複
假設樹中的某個目錄具有以下結構:
./
|__test1/
|__dirname with space
| |__test2
| |__ test2
|__dirname **
| |__test1
|
|__reboot
| |__test1
|
|__test2/
|__test3/
|__test2/
|__test1/
|__test1/
(奇怪的目錄名稱是為了證明程式碼安全性。)
您會看到一些子目錄(subdirs)以不同的方式重複。有些重複多次,而不僅僅是一次(例如test1
),一個不重複(test3
),它們可以作為父目錄和子目錄重複,也可以由任意數量的中間子目錄分隔。
下面的程式碼詳細地揭示了目錄結構中子目錄名稱的重複。
- 它解析檔案樹以取得從以下位置開始的子目錄結構
$PWD
- 它會尋找 2 個或更多層級的任何子目錄路徑的每個元件的重複項,不計算根層級( )
$PWD
。在我的實驗中,最長的子目錄路徑是:./test1/test2/test1/test3/test2/test1/test1
- 它會列印在每個子目錄層級找到的第一個子目錄副本,從葉子開始,即從右到左讀取子目錄路徑。
- 列印以相反的順序重定向到文件,因此首先顯示最長的子目錄路徑。兩個連續的分號將路徑組件(“;;”左側)與根據上一個項目符號找到的第一個重複項(“;;”右側)分開。
[代碼]
$ find ./* -type d -exec bash -c 'set -o noglob; IFS="/" subdir=($(printf "%s " "$1")); dirlevels=$((${#subdir[@]}-1)); dupe="$(awk '\''!($1 in sd) {sd[$1];next} {print $1}'\'' < <(printf "%s\n" ${subdir[@]:1}))";[ $dirlevels -ge 2 ] && [ ! -z "$dupe" ] && (printf "%s/" "${subdir[@]:1}";printf " ;; %s\n" "$(tail -n 1 < <(printf "%s\n" "$dupe"))";)' shellexec {} \; | tac >| tmp.data
$ cat -n tmp.data
1 test1/reboot/test1/ ;; test1
2 test1/dirname with space/test2/test2/ ;; test2
3 test1/test2/test1/test3/test2/test1/test1/ ;; test1
4 test1/test2/test1/test3/test2/test1/ ;; test1
5 test1/test2/test1/test3/test2/ ;; test2
6 test1/test2/test1/test3/ ;; test1
7 test1/test2/test1/ ;; test1
8 test1/dirname **/test1/ ;; test1
第 2 部分:子目錄名稱重複的處理;行動內容
處理按照 中顯示的順序進行tmp.data
。
- 在
tmp.data
的第一行,路徑上第一個被欺騙的名字./test1/test2/test1/test3/test2/test1/test1
是test1
。我們可以將其內容傳輸到最左邊的同名子目錄:./test1/
- 一旦內容被移動且沒有破壞目標處的現有文件,最右邊的子目錄層級
test1
將被刪除。 - 我們繼續執行第 2 行
tmp.data
並重複上述步驟。 - 等等,直到所有行都
tmp.data
被消耗掉。
在這個階段,問題(問題的作者:@TomDerks)是如何處理test1/*
第 6 行最右邊的內容?應該全部其內容是否被移到最左邊的同名目錄,在本例中是路徑上的第一個子目錄層級? 「全部」是否包含以下文件./test1/test2/test1/
也子目錄test3
及其內容?
完整的解決方案(第 2 部分)取決於此。