根據檔案名稱(帶空格)遞歸排序和 mv 檔案

根據檔案名稱(帶空格)遞歸排序和 mv 檔案

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

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

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

根據此鏈接,我可能將 * 或一些引號放在了錯誤的位置:mv: 無法統計 shell 腳本中沒有這樣的檔案或目錄

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

-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)。

答案1

您可以使用 和 來完成此操作findmv但這不是最簡單的方法。您使用的是 macOS,所以桀騁已預安裝。 Zsh 附帶了一個簡潔的檔案批次重命名工具,稱為zmvzsh在終端機中運行,然後在 zsh 中運行以下命令:

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

說明:

  • -n告訴zmv顯示它將做什麼,但實際上不做任何事情。當您對它顯示的內容感到滿意時,請再次運行該命令(不帶-n.
  • 第一個非選項參數是通配符模式zmv將作用於符合的文件(並忽略不符的文件)。
  • [^_]#表示 以外的零個或多個字元_。 Zsh 可讓您存取與 bash 相同的通配符等。#是僅 zsh 的功能(在 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.您的問題沒有非常精確地描述需要移動的內容以及需要移動到的位置,但是您應該能夠透過調整上面的命令來獲得您想要的內容。如果您無法弄清楚,請編輯您的問題以添加必須匹配和不得匹配的具體示例。

如果子目錄中有文件,則可以*/在模式的開頭使用。如果需要符合目錄以引用它,則需要在 : 兩邊加上括號:不能在斜線兩邊加上括號。如果您想遞歸地遍歷目錄並匹配任何債務的文件,請使用for${NUM}***/遞歸通配符。作為例外,您需要使用(**/)來存取替換文字中的目錄路徑。在這種情況下,不使用括號通常會更容易,而使用${NUM}修飾語${f}原始路徑的部分提取為整體。例如,執行與上面相同的重命名,但將檔案從目前目錄移至 下的平行結構,但在檔案名稱之前/elsewhere添加額外的層級0、等:1

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

如果很難根據名稱來匹配文件,則另一種方法是根據更改時間來匹配它們。如果您只是將一堆檔案複製到一段時間沒有更改的目錄中,則新檔案就是具有最近更改時間的檔案。您可以透過 zsh 中的更改時間來匹配文件全域限定符c。例如,要列出過去一小時內最後更改的文件:

ls -lctr *(ch-1)

您可以參考最近更改的帶有 glob 限定符的檔案oc(透過增加 ctime 進行排序)和(僅保留第一個[1,N]火柴)。例如,如果您知道剛剛將 42 個檔案移至該目錄中,並且此後沒有更改其中的任何內容:

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

如果您想將 glob 限定詞與 一起使用zmv,則需要傳遞該-Q選項。例如,將檔案移至基於檔案名稱的目錄(如上所示),但忽略過去一小時內未變更的所有檔案:

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

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

例如,如果常規檔案的名稱包含以下內容,則將常規檔案從目前目錄下移至全新的層次結構_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 的另一個功能:a歷史修改器.)

要取得檔案名稱中兩個底線之間的第一個數字,並將檔案放入以該數字命名的目錄中,您需要提取該數字。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在此網站上標記的問題

1檔案的 inode 更改時間,簡稱為“更改時間”或“ctime”,是檔案最後一次移動或最後一次更改其屬性(例如權限)的時間。它與修改時間(mtime)不同。

答案2

shell 在空格上分割輸入。您可以使用 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

相關內容