在目錄下的所有目錄上加上底線字元前綴

在目錄下的所有目錄上加上底線字元前綴

我需要重命名目錄中的多個目錄,以在名稱開頭包含下劃線“_”字元。

作為測試,我創建了一個父目錄,其內容包含三個目錄:

dir1, dir2 and dir3; three files: file1, file2, file3

和三個點前綴檔:

.file1, .file2, .file3 

到目前為止,我已經嘗試在 Mac 上gfind安裝後使用 , :findutils

gfind . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

這將返回:

mv: rename ./. to ./_.: Invalid argument

所以,我嘗試使用重命名和查找:

find . -type d -exec rename 's/^/_/' {} \;

這將返回:

Can't rename '.' to '_.': Invalid argument
Can't rename './dir1' to '_./dir1': No such file or directory
Can't rename './dir2' to '_./dir2': No such file or directory
Can't rename './dir3' to '_./dir3': No such file or directory

有人碰巧有一個可行的解決方案或知道我可能會在哪裡犯錯嗎?

答案1

如果我沒猜錯的話,你會有這樣的檔案和目錄:

$ mkdir dir{1,2,3} ; touch file{1,2,3} .file{1,2,3}
$ ls -A
.file1  .file2  .file3  dir1  dir2  dir3  file1  file2  file3

並想將目錄重命名為_dir1, _dir2, _dir3

find/printf mv看起來可行。讓我們看看它印了什麼:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n"
mv "./." "./_."
mv "./dir2" "./_dir2"
mv "./dir3" "./_dir3"
mv "./dir1" "./_dir1"

第一個命令會拋出錯誤,因為“點”很特殊,您無法重新命名它。但其他的應該沒問題,並且應該根據需要重命名目錄:

$ find . -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh
mv: cannot move ‘./.’ to ‘./_.’: Device or resource busy
$ ls -d _dir*
_dir1  _dir2  _dir3

但是透過管道將命令傳輸到 shell 有點難看,如果檔案名稱足夠奇怪,結果將會令人驚訝(例如,如果它們包含$將在 shell 中觸發變數或命令替換的符號)。

如果文件都在一個層級上,那麼應該這樣做:

for x in * ; do [ -d "$x" ] && mv "$x" "_$x" ; done

(儘管如果_$x已經存在,$x則會被移入其中。)

如果要包含名稱以點開頭的目錄,請shopt -s dotglob提前使用。

這也很接近:

find . -type d -exec rename 's/^/_/' {} \;

但由於find給了rename以 開頭的路徑./,我們需要考慮到這一點。僅在一個層面上,這應該可以(將前導更改././_):

find . -type d -exec rename 's,^./,./_,' {} \;

要取得所有層級的目錄,find -execdir可能是最容易使用的。它在檔案的目錄中運行命令。我們需要-depth以正確的順序處理重命名。

find . -depth -type d -execdir rename 's,^./,./_,' {} \;

也許也可以添加! -name .。例如

$ mkdir foo foo/dir1 foo/dir2
$ find . -depth \! -name . -type d -execdir rename 's,^./,./_,' {} \;
$ ls  _foo
_dir1  _dir2

答案2

您第一次嘗試的問題是,.除了所有子目錄之外,您還嘗試重命名自身(當前目錄)。跳過它。

gfind . -mindepth 1 -type d -name '*' -printf "mv \"%h/%f\" \"%h/_%f\"\n" | sh

但實際上,不要這樣做。切勿將東西透過管道輸入sh.這不起作用除非您的檔案名稱碰巧不包含任何 shell 特殊字元。如果您有一個名為`rm -r ~`(完全有效,但不常見的文件名稱)的文件,則 pop 會進入您的主目錄。呼叫 shell,-exec如下所示。

您的第二次嘗試不起作用,因為您_在完整路徑的開頭插入 ,而不是在基本名稱的開頭添加它,即在最後一個/.

find . -mindepth 1 -type d -exec rename 's![^/]+$!_$&!' {} \;

如果您有巢狀目錄,這些實際上都不起作用,因為它們在find遍歷目錄時會重新命名目錄。執行此操作時,需要使用選項對目錄本身進行操作,然後再對目錄內容進行操作-depth

find . -mindepth 1 -depth -type d -exec rename 's![^/]+$!_$&!' {} \;

更方便的是,呼叫 shell 並運行mv。這適用於任何 POSIX 系統。

find . -depth -type d -name . -o -exec sh -c '
    for d do mv "$d" "${d%/*}/_${d##*/}"; done
' sh {} +

如果沒有巢狀子目錄,那麼您可以使用簡單的 shell 循環。如果不想重新命名以點開頭的目錄,只需使用通配符*/,它​​只會匹配目錄和目錄的符號連結。

for d in */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi   # robustness in case there are no subdirectories
  if [ -L "$d" ]; then continue; fi     # optional: skip symbolic links to directories
  mv -- "$d" "_${d%/}"
done

如果要重新命名所有目錄(包括名稱以點開頭的目錄),請.*同時包含萬用字元。

for d in .*/ */; do
  if [ "$d" = "*/* ] && [ ! -e "$d" ]; then continue; fi
  if [ -L "$d" ]; then continue; fi
  mv -- "$d" "_${d%/}"
done

請注意,當您移動foo到時_foo,如果已經有一個名為 的目錄_foo,則會將舊目錄foo移至_foo/foo。為了防止這種情況,請新增明確測試。

或者,也可以使用 zsh。它預先安裝在 macOS 上。第一次運行autoload -U zmv(將其放入您的.zshrc或您的腳本中)以加載zmv功能,那麼非遞歸版本(包括點文件,不包括符號連結 - (/D)是一組全域限定符):

zmv -Q '*(/D)' '_$f'

或遞迴版本(使用修飾語提取目錄名和基本名):

zmv -Q '**/*(/D)' '$f:h/_$f:t'

zmv如果目標已經存在,將拒絕採取行動。

相關內容