Shell-Script

遞歸地根據文件名(帶空格)對文件進行排序和 mv

  • September 1, 2020

我犯了一個錯誤,將文件一起轉儲到同一個目錄中。幸運的是,我可以根據文件名對它們進行排序:’’’ 2019-02-19 20.18.58.ndpi_2_2688_2240.jpg ‘‘‘在這種特定情況下, #位或2是位置資訊。範圍是 0-9 並且文件名的長度都相同,因此數字將始終位於文件名的相同位置(第 26 個字元包含空格,兩側是下劃線)。我找到了這個很棒的連結: 通過僅搜尋部分名稱來查找文件的命令?

但是,我無法將輸出通過管道傳輸到 move 命令中。我試圖將輸出循環到一個變數中,但這似乎也不起作用:

for f in find . -name '*_0_*' ; do  mv "$f" /destination/directory ; done

基於此連結,我可能將 * 或一些引號放在了錯誤的位置:mv: cannot stat No such file or directory in shell script

也就是說,我有很多目錄,我想將它們分類到其他地方的相同目錄結構中:

-Flowers (to be sorted)          -Flowers-buds               -Flowers-stems
    -Roses                          -Roses                      -Roses
       buds.jpg       ===>             buds.jpg     ===>           stems.jpg
       stems.jpg
       petals.jpg
    -Daisies                       -Daisies                    -Daisies
       buds.jpgs                       buds.jpg                   stems.jpg
       stems.jpg
       petals.jpg
    -Tulips                        -Tulips                     -Tulip
       buds.jpgs                       buds.jpg                  stems.jpg
       stems.jpg
       petals.jpg

…以及更多基於該數字(#)。這是在 bash 中實用的操作嗎?我在安裝了 coreutils 的終端中執行 MacOS,因此這些工具的行為應該像 GNU linux,而不是 darwin (BSD)。

外殼在空格上拆分輸入。您可以使用帶有遞歸的 bash globbing**來正確拆分文件名:

shopt -s globstar
for f in **/*_0_*; do mv "$f" /dest/; done

如果他們都去同一個地方,則不需要循環:

shopt -s globstar
mv **/*_0_*  /dest/

… 或 use find -exec,它將文件名直接從 find 傳遞給exec()呼叫,而不需要 shell 參與:

find . -name '*_0_*' -exec mv {} /dest/ \;

或使用read加號find -print0拆分為空。對於這個問題來說,這是矯枉過正的,在通配時很有用並且find -exec不能勝任任務:

while IFS=  read -r -d ''; do mv "$REPLY" /dest/; done < <( find . -name '*_0_*' -print0 )

${var#remove_from_start}要根據文件名更改頂級目標,如您的範例所示,請使用和切斷文件名的一部分${var%remove_from_end}。刪除模式中的萬用字元將盡可能少地刪除;要盡可能多地刪除,請將操作字元加倍,如${…##…}or ${…%%…}

   shopt -s globstar
   for f in **/*_0_*; do            # Flowers/Roses/stems.jpg 
     file=${f##*/}                  # stems.jpg
     base=${file%.*}                # stems
     path=${f%/*}                   # Flowers/Roses
     toppath=${path%%/*}            # Flowers
     subpath=${path#*/}             # Roses
     dest=$toppath-$base/$subpath/  # Flowers-stems/Roses/
     [[ -d $dest ]] || mkdir -p "$dest"
     mv "$f" "$dest"
   done

您可以使用findand來執行此操作mv,但這不是這裡最簡單的方法。你在 macOS 上,所以zsh是預裝的。Zsh 帶有一個簡潔的文件批量重命名工具,稱為zmv. 在終端中執行zsh,然後在 zsh 中執行類似:

autoload zmv
zmv -n '[^_]#_([0-9])_*' '/elsewhere/${1}/${f}'

說明:

  • -n告訴zmv顯示它會做什麼,但實際上不做任何事情。當您對它顯示的內容感到滿意時,再次執行該命令而不使用-n.
  • 第一個非選項參數是萬用字元模式zmv將作用於匹配的文件(並忽略不匹配的文件)。
  • [^_]#表示除 之外的零個或多個字元_。Zsh 使您可以訪問與 bash 相同的萬用字元等。#是一個 zsh-only 功能(在 bash 中可用不同的語法),意思是“任意數量的前一個字元”。該模式[^_]#_[0-9]_*匹配任何包含兩個下劃線之間的單個數字且在該數字之前沒有其他下劃線的文件名。
  • 周圍的括號[0-9]使數字$1在替換文本中可用。
  • 替換文本可以使用${1},${2}等來指代源模式中帶括號的片段,並${f}指代完整的原始名稱。

例如,上面的程式碼片段2019-02-19 20.18.58.ndpi_2_2688_2240.jpg移至/elsewhere/2/2019-02-19 20.18.58.ndpi_2_2688_2240.jpg. 您的問題沒有非常精確地描述需要移動的內容以及需要移動到的位置,但是您應該能夠通過調整上面的命令來獲得所需的內容。如果您無法弄清楚,請編輯您的問題以添加必須匹配和不得匹配的具體範例。

如果子目錄中有文件,則可以*/在模式的開頭使用。如果你需要匹配目錄來引用它,你需要在: 周圍加上括號,你不能在斜杠周圍加上括號。如果您想遞歸地遍歷目錄並在任何債務處匹配文件,請使用遞歸globbing。作為一個例外,您需要使用訪問替換文本中的目錄路徑為. 在這種情況下,不使用括號通常更容易,而是使用修飾符將原始路徑的部分提取為一個整體。例如,進行與上述相同的重命名,但將文件從目前目錄移動到下的並行結構${*NUM*}``*``**/``(**/)``${*NUM*}``${f}``/elsewhere, 但在文件名之前有一個額外的級別0,等:1

autoload zmv
zmv -n '**/[^_]#_([0-9])_*' '/elsewhere/${f:h}/${1}/${f:t}'

如果很難根據文件名稱匹配文件,另一種方法是根據更改時間來匹配它們¹。如果您只是將一堆文件複製到一段時間未更改的目錄中,則新文件是最近更改時間的文件。您可以通過在 zsh 中使用glob 限定符c更改時間來匹配文件。例如,列出過去一小時內最後更改的文件:

ls -lctr *(ch-1)

您可以使用 glob 限定符(通過增加 ctime 進行排序)和(僅保留前N個匹配項)來引用N個最近更改的文件,而不是查找截止時間。例如,如果您知道您剛剛將 42 個文件移動到該目錄中,並且此後沒有更改任何內容:oc``[1,*N*]

ls -lctr *(oc[1,42])

如果您想將 glob 限定符與 一起使用zmv,則需要傳遞該-Q選項。例如,要將文件移動到上面基於文件名的目錄,但忽略過去一小時內未更改的所有文件:

zmv -n -Q '[^_]#_([0-9])_*(ch-1)' '/elsewhere/${1}/${f}'

zmv有一些安全措施可以檢查它是否不會覆蓋文件,並且沒有衝突(兩個源文件具有相同的目標名稱)。如果您有大量文件,這些安全措施可能需要一些時間。如果zmv太慢並且您的重命名結構保證不會發生任何衝突,您可以在循環中對您正在執行的特定重命名進行硬編碼。即使您不使用 Zsh,它也傾向於擊敗 bash,zmv這要歸功於它更好的globbing參數擴展機制。為了提高性能,您可以動態載入包含用於文件操作的內置函式的模組。

例如,將正常文件從目前目錄下移動到一個全新的層次結構,如果它們的名稱包含_0_

zmodload -m -F zsh/files 'b:zf_*'
for x in **/*_0_*(.); do
 zf_mkdir -p /destination/directory/$x:h
 zf_mv ./$x /destination/directory/$x
done

:h是另一個 zsh 功能:歷史修飾符。)

要獲取文件名中兩個下劃線之間的第一個數字,並將文件放在以該數字命名的目錄中,您需要提取該數字。zmv為帶括號的組自動執行此操作,這裡我們需要手動執行。

zmodload -m -F zsh/files 'b:zf_*'
for source in **/*_<->_*(.); do
 suffix=${${source:t}#*_<->_}
 n=${${source%$suffix}##*_}
 destination=${source:h}/$n/${source:t}
 zf_mkdir -p /destination/directory/$destination:h
 zf_mv ./$source /destination/directory/$destination
done

如果您想了解其他批量重命名文件的方法,可以瀏覽此站點上標記的問題rename

¹文件的 inode 更改時間,簡稱“更改時間”或“ctime”,是文件最後一次移動或最後一次更改其屬性(例如權限)的時間。它不同於修改時間(mtime)。

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