ubuntu 如何在特定条件下重命名文件夹中的文件?

qmelpv7a  于 9个月前  发布在  其他
关注(0)|答案(5)|浏览(121)
S_1004_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_1_fastqc.html
S_1004_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_1_fastqc.zip
S_1004_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_2_fastqc.html
S_1004_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_2_fastqc.zip
S_1006_DKDL220006298-1A_HKFTLDSX3_L1_cleaned_1_fastqc.html
S_1006_DKDL220006298-1A_HKFTLDSX3_L1_cleaned_1_fastqc.zip
S_1006_DKDL220006298-1A_HKFTLDSX3_L1_cleaned_2_fastqc.html

字符串
上面是文件夹中文件的名称。我想从右数第二个_和左数第二个_之间删除。这样输出看起来像

S_1004__1_fastqc.html
S_1004__1_fastqc.zip
S_1004__2_fastqc.html
S_1004__2_fastqc.zip
S_1006__1_fastqc.html
S_1006__1_fastqc.zip
S_1006__2_fastqc.html


如何使用bash实现这一点?
我尝试了以下代码:

for file in *.html *.zip; do
  new_name=$(echo "$file" | sed 's/_[^_]*_/_/')
  mv "$file" "$new_name"
done


但它并没有按照我想要的方式工作。

o75abkj4

o75abkj41#

您不需要sed来执行此操作(但请参阅最后的解释,了解尝试失败的原因以及工作的sed命令)。
使用足够新的bash[[ string =~ regexp ]]BASH_REMATCH至少为3.0):

for f in *.html *.zip; do
  [[ "$f" =~ ^(([^_]*_){2}).+((_[^_]*){2})$ ]] && mv "$f" "${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
done

字符串
一个老的bash:

for f in *.html *.zip; do
  set -f; ifs="$IFS"; IFS=_ a=( $f ); IFS="$ifs"; set +f; n="${#a[@]}"
  (( n > 4 )) && mv "$f" "${a[0]}_${a[1]}__${a[n-2]}_${a[n-1]}"
done


注意:set -f; ...; set +f暂时禁止路径名扩展,因为您的文件名可能包含glob运算符(*?[...])。
尝试失败的原因是sed试图从左到右匹配正则表达式(并且是贪婪的)。在sed 's/_[^_]*_/_/'中,最左边的匹配被替换。如果文件名为S_1004_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_1_fastqc.html,则匹配部分为_1004_(最左边),结果为S_DKDL220006264-1A_HLGLFDSX3_L4_cleaned_1_fastqc.html
如果你真的想使用sed,你可以尝试:

sed 's/^\([^_]*_[^_]*_\).\+\(_[^_]*_[^_]*\)$/\1\2/'

k5ifujac

k5ifujac2#

新的文件名可以使用POSIX shell中内置的字符串操作功能来计算。尝试以下Shellcheck-clean代码:

#! /bin/sh -

for file in *_*_*_*_*.*; do
    [ -f "$file" ] || continue

    suffix=${file##*.}
    [ "$suffix" = zip ] || [ "$suffix" = html ] || continue

    base=${file%.*}
    tmp=${base#*_*_}
    start=${base%"$tmp"}
    tmp=${base%_*_*}
    end=${base#"$tmp"}

    newfile=${start}${end}.${suffix}

    echo mv -v -n -- "$file" "$newfile"
done

字符串

  • 我用bashdash/bin/sh引用的最常见的shell)对此进行了测试。
  • [ -f "$file" ] || continue如果当前匹配的文件不是常规文件,则跳过当前匹配的文件。这可能是因为它是其他文件类型(例如,目录或fifo),或者因为没有文件匹配该模式(因此$file的文字值为*_*_*_*_*.*)。
  • 有关${file##*.}${file*.*}和其他字符串操作的解释,请参见POSIX Shell Command Language文档的参数扩展部分。
  • 如果您对代码能够按照您的要求运行感到满意,请删除echo
9o685dep

9o685dep3#

尝试使用awk而不是sed

for file in *.html *.zip; do
  new_name=$(echo "$file" | awk -F"_" '{print $1"_"$2"_"$NF}')
  mv "$file" "$new_name"
done

字符串

ve7v8dk2

ve7v8dk24#

如果你正在寻找一个awk脚本来实现上述解决方案。我会将字段分隔符设置为_,然后打印所需的字段(第一个,第二个,最后一个,但第二个和最后一个字段),第一个,第二个和最后一个由_分隔,最后一个,但第二个由__分隔。NF是预定义的变量。
以下是示例:

files=("All_files")

for file in "${files[@]}"; do
  new_name=$(echo "$file" | awk -F '_' '{print $1 "_" $2 "__" $(NF-1) "_" $NF}')
  echo "$file -> $new_name"
  # mv "$file" "$new_name" # uncomment to rename them actually
done

字符串
脚本演示

替代方案(使用perl):

files=("All_files")
for file in "${files[@]}"; do
  new_name=$(echo "$file" | perl -pe 's/(S_\d+)_.*_.*_(.*_.*)/\1__\2/')
  # currently the regex used with perl can accept any character.
  # If you require only word characters(i.e. [A-Za-z0-9_]), replace .* with \w*
  echo "Renaming: $file -> $new_name"
  # mv "$file" "$new_name"
done


脚本演示

rlcwz9us

rlcwz9us5#

如果你想给予一下Perl一行程序:

perl -pe '{s/(^.+_\d+_).+(_\d_fastqc.+)/\1\2/}' file

字符串
-e:允许您在命令行上输入程序。
-p:假设程序有一个循环,如:while(<>){...}。它也会打印这行,所以print语句是不必要的(将它与下面的-n进行比较)。
-n:假设一个while循环while(<>){...}。但是除非你显式地打印它,否则它不会打印该行。这不是程序的一部分,但是我在这里把它和-p进行比较。如果你用-n写了同样的一行程序,你的程序会像这样:
perl -ne '{$_ =~ s/(^.+_\d+_).+(_\d_fastqc.+)/\1\2/; print $_}' file的值。该值为
$_:这是Perl的默认变量。实际上,您可以省略它,并将其写成:
perl -pe '{s/(^.+_\d+_).+(_\d_fastqc.+)/\1\2/}' file
s///:这是Perl的替换运算符。例如,s/old/new/ #Replaces old with new.
剩下的就是正则表达式了(我相信你可以用很多不同的方法来写这一部分,下面是一种方法):
s/(^.+_\d+_).+(_\d_fastqc.+)/\1\2/:这将匹配每行。
(^.+_\d+_):从行首到第二个下划线得结尾进行捕获.它被\1捕获.
(_\d_fastqc.+):匹配从左起第二个下划线右侧的任何内容。由\2捕获。
\1捕获第一组()的内容,而\2捕获第二组()的内容。
要了解它们的作用,最好的方法是只使用\1或\2运行。
简而言之:您只使用行的捕获部分替换整行,因此非捕获文本将被删除

相关问题