2009-03-02 372 views
73

Bash如何重命名一系列軟件包以刪除其版本號?我一直在玩expr%%,無濟於事。使用Bash批量重命名文件

例子:

Xft2-2.1.13.pkg成爲Xft2.pkg

jasper-1.900.1.pkg成爲jasper.pkg

xorg-libXrandr-1.2.3.pkg成爲xorg-libXrandr.pkg

+1

我打算經常使用它,作爲一次寫入使用的批量腳本。 我將使用的任何系統都會有打擊,所以我不怕那些非常方便的bashisms。 – 2009-03-02 15:45:40

+0

更普遍的另請參閱https://stackoverflow.com/questions/28725333/looping-over-pairs-of-values-in-bash – tripleee 2018-03-04 19:19:14

回答

138

你可以使用bash的參數擴展功能都需要有空格的文件名

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done 

行情。

+1

我喜歡這個迄今爲止最好的! – 2009-03-02 15:36:46

+1

我也喜歡它,但它使用bash特有的功能......我通常不會使用它們來支持「標準」sh。 – 2009-03-02 15:39:05

3

更好地利用sed對於這一點,是這樣的:

find . -type f -name "*.pkg" | 
sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' | 
while read nameA nameB; do 
    mv $nameA $nameB; 
done 

搞清楚了正則表達式作爲一個練習(如處理那些包含空格的文件名)

+0

謝謝:名稱中不使用空格。 – 2009-03-02 15:34:47

1

這似乎是工作假設

  • 一切以$ PKG
  • 你的版本結束「 - 」

條斷的.pkg,然後脫掉 - ..

#的總是以一個開始
for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done 
+0

有些軟件包的名稱中有一個連字符(請參閱第三個示例)。這會影響你的代碼嗎? – 2009-03-02 15:35:54

+1

您可以使用`-e`運行多個sed表達式。例如。 `sed -e's/\。pkg // g'-e's /-.*// g'`。 – nojak 2017-08-31 23:05:42

+0

[不要解析`ls`輸出](https://mywiki.wooledge.org/ParsingLs)和[引用您的變量]。(/ q/10067266)另請參閱http://shellcheck.net/用於診斷常見問題和shell腳本中的反模式。 – tripleee 2018-03-04 19:30:14

7

我會做這樣的事情:

for file in *.pkg ; do 
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg 
done 

認爲所有的文件都在當前目錄中。如果不是,請按照Javier的建議使用find。

編輯此外,此版本不使用任何bash特有的功能,如上面的其他功能,這將導致您更多的便攜性。

23

如果所有文件都在同一目錄序列

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh 

會做你的工作。命令sed命令將創建一系列命令,然後您可以將它們傳送到shell中。最好首先運行流水線,不要拖尾| sh以驗證命令是否符合您的要求。

遞歸通過多個目錄中使用類似

find . -type f | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh 

注意,在sed的正則表達式分組序列是一個反斜槓,\(\),而不是單一的支架()前支架。

0

謝謝你的回答。我也有一些問題。將.nzb.queued文件移動到.nzb文件。它有文件名中的空格和其他文件,這解決了我的問題:

find . -type f -name "*.nzb.queued" | 
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" | 
sh 

它基於Diomidis Spinellis的回答。

正則表達式爲整個文件名創建一個組,爲.nzb.queued之前的部分創建一個組,然後創建一個shell移動命令。引用字符串。這也避免了在shell腳本中創建一個循環,因爲這已經由sed完成了。

1

我有多個*.txt文件被重命名爲.sql在同一個文件夾中。下面 工作對我來說:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done 
1

這裏是一個POSIX近相當於currently accepted answer的。這交換了僅限Bash的${variable/substring/replacement}參數擴展,可用於任何Bourne兼容shell。

for i in ./*.pkg; do 
    mv "$i" "${i%-[0-9.]*.pkg}.pkg" 
done 

參數擴展${variable%pattern}產生的variable值與它匹配pattern除去任何後綴。 (也有${variable#pattern}刪除前綴。)

我保留子模式-[0-9.]*從接受的答案,雖然它也許是誤導。這不是一個正則表達式,而是一個glob模式;所以它並不意味着「破折號後跟零個或多個數字或點」。相反,它的意思是「短劃線,後面跟着一個數字或一個點,然後是任何東西」。 「任何事物」將是最短的匹配,而不是最長的。 (修剪最長可能前綴或後綴,而不是最短的Bash提供##%%

0

我們可以假設sed可在任何* nix的,但我們不能確定 它會支持sed -n到生成mv命令。 (注意:只有GNU sed這樣做。)

即使如此,bash內置和sed,我們可以快速掀起一個shell函數來做到這一點。

sedrename() { 
    if [ $# -gt 1 ]; then 
    sed_pattern=$1 
    shift 
    for file in $(ls [email protected]); do 
     mv -v "$file" "$(sed $sed_pattern <<< $file)" 
    done 
    else 
    echo "usage: $0 sed_pattern files..." 
    fi 
} 

使用

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg 

before: 

./Xft2-2.1.13.pkg 
./jasper-1.900.1.pkg 
./xorg-libXrandr-1.2.3.pkg 

after: 

./Xft2.pkg 
./jasper.pkg 
./xorg-libXrandr.pkg 

創建目標文件夾:

由於mv不會自動創建,我們不能使用 我們的sedrename最初版本的目標文件夾。

這是一個相當小的變化,所以它會是不錯的包含功能:

我們需要一個效用函數,abspath(或絕對路徑),因爲慶典 沒有這個版本英寸

abspath() { case "$1" in 
       /*)printf "%s\n" "$1";; 
       *)printf "%s\n" "$PWD/$1";; 
      esac; } 

一旦我們有了我們可以生成用於sed的一個 目標文件夾(或多個)/重命名圖案,其中包括新的文件夾結構。

這將確保我們知道我們的目標文件夾的名稱。當我們 重命名時,我們需要在目標文件名上使用它。

# generate the rename target 
target="$(sed $sed_pattern <<< $file)" 

# Use absolute path of the rename target to make target folder structure 
mkdir -p "$(dirname $(abspath $target))" 

# finally move the file to the target name/folders 
mv -v "$file" "$target" 

下面是完整的文件夾意識到腳本...

sedrename() { 
    if [ $# -gt 1 ]; then 
    sed_pattern=$1 
    shift 
    for file in $(ls [email protected]); do 
     target="$(sed $sed_pattern <<< $file)" 
     mkdir -p "$(dirname $(abspath $target))" 
     mv -v "$file" "$target" 
    done 
    else 
    echo "usage: $0 sed_pattern files..." 
    fi 
} 

當然,當我們沒有具體的目標文件夾 過它仍然有效。

如果我們想要把所有的歌曲放到一個文件夾,./Beethoven/我們可以這樣做:

使用

sedrename 's|Beethoven - |Beethoven/|g' *.mp3 

before: 

./Beethoven - Fur Elise.mp3 
./Beethoven - Moonlight Sonata.mp3 
./Beethoven - Ode to Joy.mp3 
./Beethoven - Rage Over the Lost Penny.mp3 

after: 

./Beethoven/Fur Elise.mp3 
./Beethoven/Moonlight Sonata.mp3 
./Beethoven/Ode to Joy.mp3 
./Beethoven/Rage Over the Lost Penny.mp3 

獎金圓...

使用這個腳本從移動文件文件夾放到一個文件夾中:

假設我們想收集所有匹配的文件,並將它們放在當前的f年紀大了,我們可以做到這一點:在SED正則表達式模式

sedrename 's|.*/||' **/*.mp3 

before: 

./Beethoven/Fur Elise.mp3 
./Beethoven/Moonlight Sonata.mp3 
./Beethoven/Ode to Joy.mp3 
./Beethoven/Rage Over the Lost Penny.mp3 

after: 

./Beethoven/ # (now empty) 
./Fur Elise.mp3 
./Moonlight Sonata.mp3 
./Ode to Joy.mp3 
./Rage Over the Lost Penny.mp3 

定期SED模式規則適用於該腳本,這些模式都沒有 PCRE(Perl兼容正則表達式)。根據您的平臺,您可以使用sed -rsed -E 擴展正則表達式語法sed 。

有關 sed基本和擴展正則表達式模式的完整說明,請參閱POSIX兼容man re_format