用文件名中的下划线替换点,保留扩展名不变


8

我有一个bash脚本,我试图替换文件名中的点,并用下划线替换它们,使扩展名保持不变(我在Centos 6 btw上)。从下面的输出中可以看到,该脚本在有一个点要替换的情况下起作用,但是在唯一的点是扩展名的情况下,该脚本仍会尝试重命名该文件,而不是忽略它。谁能指出我应该如何更好地处理这个问题?谢谢你的帮助。

我的(错误的)脚本:

#!/bin/bash

for THISFILE in *
do
  filename=${THISFILE%\.*}
  extension=${THISFILE##*\.}
  newname=${filename//./_}
  echo "mv $THISFILE ${newname}.${extension}"
  #mv $THISFILE ${newname}.${extension}
done

输入样例:

1.3MN-Pin-Eurotunnel-Stw505.51.024-EGS-130x130.jpg
Wear-Plates.jpg

输出:

mv 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg 1_3MN-Pin-Eurotunnel-Stw505_51_024-EGS1-130x130.jpg
mv Wear-Plates_jpg.Wear-Plates_jpg Wear-Plates_jpg.Wear-Plates_jpg

1
那棘手的情况,例如tar.gz文件呢?您希望他们解决file.tar.gz,而不是file_tar.gz
IQAndreas 2014年

Answers:


10

我相信这个程序会做您想要的。我已经对其进行了测试,并且可以在几种有趣的情况下使用(例如完全不扩展):

#!/bin/bash

for fname in *; do
  name="${fname%\.*}"
  extension="${fname#$name}"
  newname="${name//./_}"
  newfname="$newname""$extension"
  if [ "$fname" != "$newfname" ]; then
    echo mv "$fname" "$newfname"
    #mv "$fname" "$newfname"
  fi
done

您遇到的主要问题是##扩展没有按照您的要求进行。我一直认为bash中的shell参数扩展是一门妖术。手册中的解释尚不完全清楚,并且缺少有关扩展应如何工作的任何支持示例。他们也很神秘。

就我个人而言,我会sed以自己想要的名称来编写一个小脚本,或者以perl整个过程来编写一个小脚本。回答该问题的其他人之一。

我想指出的另一件事是我使用引号。每当我对shell脚本执行任何操作时,我都会提醒人们在引用时要非常小心。Shell脚本中大量的问题源是Shell解释了原本不应该的内容。而且报价规则远非显而易见。我相信这个shell脚本没有引用问题。


这确实确实很好地完成了工作:)谢谢您的解释,也重新介绍了shell扩展。
bsod99

除了用(_)替换点(。)之外,如何删除文件名中的空格。
discipulus's

这是一个非常好的脚本。我能看到的唯一改进是使它递归了目录树,并允许您用$ 2替换$ 1,但是这些都是次要的。(或者,就像我的老师过去所说的,“留给学生练习”!)
lbutlr

4

采用 for thisfile in *.*.*(即,循环名称中包含两个或多个点的文件)。请记住用引号引起来,并使用--来标记选项的结尾mv -- "$thisfile" "$newname.$extension"

用zsh。

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'

我可能错误地实施了您的建议,但这导致了- 。* _。*
bsod99 2012年

3

这个怎么样:

perl -e '
         @files = grep {-f} glob "*";
         @old_files = @files;
         map {
              s!(.*)\.!$1/!;
              s!\.!_!g;
              s!/!.!
             } @files;
         rename $old_files[$_] => $files[$_] for (0..$#files)
        '

免责声明:首先在虚拟目录上尝试,我还没有测试!


简洁,精巧,几乎只写!是的,Perl!轻笑
五花八门的

只写?我敢打赌,这是Perl第一次被这样称呼再次轻笑
约瑟夫R.12年

尝试改善那里的可读性。希望这感觉不太“只写” :)
Joseph R.

1
是的,这可以帮助很多人。遗憾的是,它是唯一可以用作占位符的合理字符/
2012年

2

看起来已经有一些不错的答案,但这是另一个使用trand的方法sed

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done

如何sed决定对哪个对象.*应用最大的嚼食?如果是第二个.*,我会感觉好多了[^_]*
2012年

我还认为,这取决于“无扩展名”和带有\t字符的文件名的情况。
2012年

这绝对是一个快速的解决方案。感谢您的输入...我修复了正则表达式,以适应您的第一个建议。我将看看是否可以调整它以解决您提到的其他情况。
JC Yamokoski 2012年

echo -n "$file"会工作。:-)哦,表达式(aka )"周围会用。$( ... )"$( ... )"
2012年

1

此版本使您可以从右侧开始显式选择要保留的点数。

此外,它将替换和/或删除除点以外的其他字符,并且替换字符-不是下划线,但是可以轻松更改。

#!/bin/sh
# Rename files by replacing Unix-unfriendly characters.

usage () {
    cat <<EOF
usage: $0 [OPTIONS] [--] [FILE [FILE...]]
Rename files by replacing Unix-unfriendly characters.

Options:
 -p N              preserve last N dots in filename, or keep all
                   dots if N < 0 (default: 1)
       --help      show this help and exit
EOF
}

error () {
    printf "%s\n" "$1" 1>&2
}

delete_chars="()[]{}*?!^~%\\\<>&\$#|'\`\""
replace_chars=" _.,;-"

unixify_string () (
    printf '%s\n' "$1" \
        | tr -d "$delete_chars" \
        | tr -s "$replace_chars" - \
        | to_lower \
        | sed 's/^-\(.\)/\1/; s/\(.\)-$/\1/'
)

to_lower () {
    sed 's/.*/\L&/'
}

split () (
    # split '.x.x.x.x'  0 -> '/x.x.x.x.x
    # split '.x.x.x.x'  1 -> '/x.x.x.x/x
    # split '.x.x.x.x'  2 -> '/x.x.x/x/x
    # split '.x.x.x.x' -1 -> '/x/x/x/x/x
    nf=$(printf '%s\n' "$1" | tr -d -C . | wc -c)
    if [ $2 -lt 0 ]; then
        keep=0
    else
        keep=$((nf-$2))
    fi
    IFS=. i=0 out= sep=
    for part in $1; do
        out="$out$sep$part"
        if [ -z "$out" -o $i -ge $keep ]; then
            sep=/
        else
            sep=.
        fi
        i=$(($i+1))
    done
    printf '%s\n' "$out"
)

unixify () (
    IFS=/ out= sep=
    for part in $(split "$1" $2); do
        out="$out$sep$(unixify_string "$part")"
        sep=.
    done
    printf '%s\n' "$out"
)

rename_maybe () (
    dir="$(dirname "$1")"
    name="$(basename "$1")"
    newname="$(unixify "$name" $2)"
    if [ "$newname" != "$name" ]; then
        mv -i "$dir/$name" "$dir/$newname"
    fi
)

# command line arguments

short_opts=p:
long_opts=help

args="$(LC_ALL=C getopt -n "$0" -s sh -o $short_opts -l $long_opts -- "$@")"
if [ $? -eq 0 ]; then
    eval set -- "$args"
else
    exit 1
fi

p=
while [ $# -gt 0 ]; do
    case "$1" in
        --help)
            usage; exit 0 ;;
        -p)
            p="$2"; shift
            if ! [ "$p" -eq "$p" ] 2> /dev/null; then
                error "$0: option requires integer argument -- 'p'"
                exit 1
            fi ;;
        --)
            shift; break ;;
        -*)
            error "$0: illegal option -- '$1'"
            exit 1 ;;
        *)
            break
    esac
    shift
done

# defaults
p=${p:-1}

# echo p=$p
# echo "$@"
# echo n=$#
# exit

if [ $# -lt 1 ]; then
    error "$0: required non-option argument missing"
    exit 1
fi

for file in "$@"; do
    rename_maybe "$file" $p
done
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.