我有一個 OSX 腳本,用於查找僅包含一個文件的資料夾,如果該文件是音訊文件,則輸出音訊文件的路徑
find "$1" -type d -exec sh -c '[[ $(find "$0" -mindepth 1 | wc -l) -eq 1 ]]
&& [[ $(find "$0" -mindepth 1 -type d | wc -l) -eq 0 ]]
&& find "$0"' {} \; |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"
即使用像
./findodd.sh /Users/paul/Music
但我需要兩個改進:
我可以更改什麼,以便它列出包含 2 個文件、3 個文件等的資料夾中的文件,如果可以將其作為參數傳遞,那就更好了
目前它找到只包含一個文件的文件夾,並且該文件必須是一個音頻文件,但我真正想要它做的是找到只包含一個音頻文件的文件夾,即如果該文件夾包含三個文件但只有一個是音訊檔案我希望列出該音訊檔案。
謝謝保羅
答案1
$ find
.
./folder3
./folder3/quux.txt
./folder1
./folder1/test.mp3
./folder1/test.txt
./folder1/test.wma
./folder2
./folder2/bar.txt
./folder2/foo.txt
./folder2/test.ogg
運行範例:
$ ./findaudio.sh /tmp/findaudio 1
/tmp/findaudio/folder2/test.ogg
$ ./findaudio.sh /tmp/findaudio 2
/tmp/findaudio/folder1/test.mp3
/tmp/findaudio/folder1/test.wma
# The first parameter defaults to the current directory and
# the second parameter defaults to 1 so this works as well:
$ ./findaudio.sh
./folder2/test.ogg
這裡是程式碼:
#!/bin/bash
shopt -s nullglob
find "${1:-.}" -type d | while read dir; do
files=( "${dir}"/*.{mp4,mp3,ogg,flac,wma,m4a} )
IFS=$'\n'
(( ${#files[@]} == ${2:-1} )) && echo "${files[*]}"
done
它迭代給定目錄的所有子目錄,並使用 globbing 將當前子目錄的所有音訊檔案名稱讀入數組files
。如果陣列的大小與您想要的值匹配,它只會列印出由換行符分隔的檔案名稱。
編輯:這是我之前的方法,基於您想要列印資料夾而不是有問題的檔案名稱的假設。我將把它留在這裡以供將來參考。
$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -u
./folder2
其作用是尋找具有列出的音訊副檔名的所有文件,並僅列印其目錄組件而不是完整路徑。這將為您提供所有音訊檔案的父資料夾清單。跳過uniq
非唯一的行,這應該會給你你想要的結果,即只列印包含一個音訊檔案的資料夾。
從理論上講,這應該比您之前的嘗試快很多。
您可以對此進行改進,透過計算重複行並僅列印與您要求的計數相符的資料夾來滿足您的第一點。一個天真的解決方案是:
$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=1 '$1==count'
1 ./folder2
$ find . \( -name '*.ogg' -o -name '*.wma' -o -name '*.mp3' \) -printf "%h\n" | uniq -c | awk -v count=2 '$1==count'
2 ./folder1
uniq
儘管將管道的 - 部分和右側融合在一條線上可能會更好awk
。
答案2
第二次嘗試
好的,在我自己的音樂資料夾上實際嘗試後,這就是您的兩個請求的解決方案:
COMMAND='[[ $(find "$0" -maxdepth 2 |egrep "\.mp4|\.mp3|\.ogg|\.flac|\.wma|\.m4a"| wc -l) == '$2' ]] && echo "$0"'
find $1 -type d -exec sh -c "$COMMAND" {} \;
所以你的腳本有一些問題:
- 您正在使用
mindepth
而不是maxdepth
. - egrep 中的句點 (.) 可以符合任何字元。所以
.wma
會匹配“Snowman.txt”。 - 您不需要對類型「d」進行第二次測試,因為只有目錄會傳遞到 shell 命令中。
我的腳本註解:
- 用法是:
findodd.sh <top_folder> <no_of_files>
- 報價很關鍵。的定義
COMMAND
其實是 兩邊的 2 個字串文字$2
。這真的很重要。 - 它僅列出包含文件的資料夾,而不列出文件本身。要執行後者,您必須將 替換
echo "$0"
為另一個find
.
現在我已經在 Arch Linux 機器上進行了測試,我的 shell 是“bash”,所以我不知道這是否適用於 OSX,因為所有 shell 的創建方式並不相同。 :-)
早期的第一次嘗試:
嗯嗯。我不知道 OSX 與 Unix/Linux 有多相似,但我會嘗試一下。
我相信,您兩個問題的答案在於“sh -c”命令的第一次測試。就是這樣寫的:
$(find "$0" -mindepth 1 | wc -l) -eq 1
要將檔案數量傳遞給腳本的第二個參數,您應該能夠將 '1' 更改為 $2,因此測試將是:
$(find "$0" -mindepth 1 | wc -l) -eq $2
不要在 周圍加上引號$2
,否則它將被解釋為傳遞給「sh -c」命令的第二個參數,而不是您的腳本。
命令列將是:
./findodd.sh /Users/paul/Music 2
據我了解,為了實現第二個要求,您需要將命令放入egrep
第一個測試中,因此:
$(find "$0" -mindepth 1 |egrep ".mp4|.mp3|.ogg|.flac|.wma|.m4a"| wc -l) -eq $2
不過,您可能需要注意報價。
不管怎樣,請嘗試並讓我們知道。
答案3
您可以透過執行以下操作在 Python 中實現此功能:
#!/usr/bin/env python
import fnmatch
import os
import sys
if len(sys.argv) != 3 or \
not sys.argv[1].isdigit() or \
not os.path.exists(sys.argv[2]):
print "Usage: %s [number of files] [search root]" % sys.argv[0]
sys.exit(1)
num_files = int(sys.argv[1])
search_root = sys.argv[2]
# this must be a tuple to work with endswith()
audio_extensions = (
'mp4',
'mp3',
'ogg',
'flac',
'wma',
'm4a',
)
for dirpath, dirnames, filenames in os.walk(search_root):
audio_files = [f for f in filenames if f.endswith(audio_extensions)]
if len(audio_files) == num_files:
print "\n".join([os.path.join(dirpath, f) for f in audio_files])
如果您chmod +x findodd.py
可以按照與運行當前腳本相同的方式運行它,例如:
./findodd.py 1 /Users/paul/Music