Bash脚本来更改不同扩展名的文件名

时间:2019-01-09 14:15:30

标签: linux bash

我们正在开发一个angularjs项目,该项目的编译输出包含许多文件扩展名,例如js,css,woff等。以及作为文件名一部分的单个动态哈希。

我正在研究简单的bash脚本,以搜索属于上述文件扩展名的文件,并将其移至某个文件夹,并删除哈希 搜索“。”的第一个实例。

请注意文件扩展名.woff和.css应该保留。

/src/main.1cc794c25c00388d81bb.js   ==> /dst/main.js  
/src/polyfills.eda7b2736c9951cdce19.js ==> /dst/polyfills.js  
/src/runtime.a2aefc53e5f0bce023ee.js ==> /dst/runtime.js  
/src/styles.8f19c7d2fbe05fc53dc4.css ==> /dst/styles.css  
/src/1.620807da7415abaeeb47.js  ==> /dst/1.js  
/src/2.93e8bd3b179a0199a6a3.js  ==> /dst/2.js  
/src/some-webfont.fee66e712a8a08eef580.woff  ==> /dst/some-webfont.woff  
/src/Web_Bd.d2138591460eab575216.woff ==> /dst/Web_Bd.woff  

邮政编码:

#!/bin/bash
echo Process web binary files!

echo Processing the name change for js files!!!!!!!!!!!!!
sfidx=0;
SFILES=./src/*.js #{js,css,voff}
DST=./dst/

for files in $SFILES
do
        echo $(basename $files)
        cp $files ${DST}"${files//.*}".js  
        sfidx=$((sfidx+1))
done
echo Number of target files detected in srcdir $sfidx!!!!!!!!!!

上面的代码有2个问题, 需要在公共位置的for循环中添加文件扩展名,而不是为每个扩展名运行。但是,此方法失败,不确定是否需要更改。

SFILES=./src/*.{js,css,voff}
cp: cannot stat `./src/*.{js,css,voff}': No such file or directory

第二,由于以下原因导致cp cmd失败,需要一些帮助以找出正确的语法。

cp $files ${DST}"${files//.*}".js  
1.620807da7415abaeeb47.js
cp: cannot create regular file `./dst/./src/1.620807da7415abaeeb47.js.js': No such file or directory

4 个答案:

答案 0 :(得分:2)

这是一个相对简单的命令:

find ./src -type f \( -name \*.js -o -name \*.css -o -name \*.woff \) -print0 | 
while IFS= read -r -d $'\0' line; do
    dest="./dst/$(echo $(basename $line) | sed -E 's/(\..{20}\.)(js|css|woff)/\.\2/g')"
    echo Copying $line to $dest 
    cp $line $dest
done

答案 1 :(得分:1)

这是基于原始代码并且是Shellcheck干净的:

#!/bin/bash

shopt -s nullglob       # Make globs that match nothing expand to nothing

echo 'Process web binary files!'

echo 'Processing the name change for js, css, and woff files!!!!!!!!!!!!!'
srcfiles=( src/*.{js,css,woff} )
destdir=dst

for srcpath in "${srcfiles[@]}" ; do
    filename=${srcpath##*/}
    printf '%s\n' "$filename"

    nohash_base=${filename%.*.*}    # Remove the hash and suffix from the end
    suffix=${filename##*.}          # Remove everything up to the final '.'
    newfilename=$nohash_base.$suffix

    cp -- "$srcpath" "$destdir/$newfilename"
done

echo "Number of target files detected in srcdir ${#srcfiles[*]}!!!!!!!!!"

该代码使用数组而不是字符串来保存文件列表,因为它更容易(而且通常更安全,因为它可以处理名称包含空格和其他特殊字符的文件)。有关在Bash中使用数组的信息,请参见Arrays [Bash Hackers Wiki]

有关使用${var##pattern}等来提取部分字符串的信息,请参见Removing part of a string (BashFAQ/100 (How do I do string manipulation in bash?))

有关为何最好避免使用大写变量名(例如SFILES)的说明,请参见Correct Bash and shell script variable capitalization

shopt -s nullglob可防止在glob模式不匹配时发生奇怪的事情。有关更多信息,请参见Why is nullglob not default?

请参阅Bash Pitfalls #2 (cp $file $target),以了解为什么通常最好使用cp --而不是普通的cp(尽管在这种情况下没有必要(因为两个参数都不能以'-'开头))。

最好保持Bash代码Shellcheck干净。当在问题中的代码上运行时,它可以识别关键问题,并建议使用数组作为解决问题的方法。它还确定了其他一些潜在问题。

答案 2 :(得分:0)

您的问题是扩展的重点。这是我的解决方案:

#!/bin/bash
echo Process web binary files!

echo Processing the name change for js files!!!!!!!!!!!!!
sfidx=0;
SFILES=$(echo ./src/*.{js,css,voff})
DST=./dst/

for file in $SFILES
do
        new=${file##*/}                  # basename
        new="${DST}${new%\.*}.js"        # escape \ the .            
        echo "copying $file to $new"     # sanity check
        cp $file "$new"
        sfidx=$((sfidx+1))
done
echo Number of target files detected in srcdir $sfidx!!!!!!!!!!

在./src中有三个文件都被命名为“ gash”,我得到了:

Process web binary files!
Processing the name change for js files!!!!!!!!!!!!!
copying ./src/gash.js to ./dst/gash.js
copying ./src/gash.css to ./dst/gash.js
copying ./src/gash.voff to ./dst/gash.js
Number of target files detected in srcdir 3!!!!!!!!!!

(您也许可以使用eval来解决这个问题,但这可能是一个安全问题)

new=${file##*/}-删除以/结尾的左侧最长的字符串(删除前导目录名称,如basename)。如果您想使用外部非外壳basename程序,那么它将是new=$(basename $file)

${new%\.*}-从.开始删除最短的字符串(删除旧的文件扩展名)

答案 3 :(得分:0)

一种可行的方法是让find命令生成一个shell脚本,然后执行它。

src=./src
dst=./dst
find "$src" \( -name \*.js -o -name \*.woff -o -name \*.css \) \
  -printf 'p="%p"; f="%f"; cp "$p" "'"${dst}"'/${f%%%%.*}.${f##*.}"\n'

这将打印您要执行的shell命令。如果他们是你想要的 只需将输出通过管道传递到外壳:

find "$src" \( -name \*.js -o -name \*.woff -o -name \*.css \) \
  -printf 'p="%p"; f="%f"; cp "$p" "'"${dst}"'/${f%%%%.*}.${f##*.}"\n'|bash

(或|bash -x,如果您想查看发生了什么。)

如果您有名为./src/dir1/a.xyz.js./src/dir2/a.uvw.js的文件,它们都将以./dst/a.js结尾,第二个将覆盖第一个。为避免这种情况,您可能要使用cp -i而不是cp

如果您完全确定路径名中绝不会包含空格或其他奇怪的字符,则可以使用较少的引号(对某些shell纯粹主义者而言是恐怖的)

find $src \( -name \*.js -o -name \*.woff -o -name \*.css \) \
  -printf "p=%p; f=%f; cp \$p ${dst}/\${f%%%%.*}.\${f##*.}\\n"|bash

一些最后的评论:

  • %p%f-printf扩展为已处理文件的完整路径名和基名。它们使我们能够避免使用basename命令。不幸的是,文件扩展名没有这样的指令,因此我们必须在shell中使用大括号扩展来组成最终名称。

  • -printf参数中,我们必须使用%%来写一个百分号字符。由于我们需要两个,所以必须有四个...

  • ${f%%.*}在shell中随着$f的值扩展,并且从第一个点开始都删除了所有内容

  • ${f##*.}在shell中扩展为$f的值,并将所有内容删除到最后一个点(即,扩展为文件扩展名)