ファイル名のドットをアンダースコアに置き換え、拡張子はそのままにします


8

ファイル名のドットを置き換えてアンダースコアに置き換えようとするbashスクリプトがありますが、拡張子はそのままにしておきます(私はCentos 6を使用しています)。以下の出力からわかるように、置換するドットがある場合でもスクリプトは機能しますが、ドットのみが拡張子である場合、スクリプトはファイルを無視するのではなく、ファイルの名前を変更しようとします。誰かがこれをよりよく処理する方法を指摘できますか?助けてくれてありがとう。

私の(欠陥のある)スクリプト:

#!/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

回答:


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でのシェルパラメーターの展開を何か黒の芸術だと考えてきました。マニュアルの説明は完全に明確ではなく、拡張がどのように機能するかのサポート例がありません。彼らはまたかなり不可解です。

個人的には、私がsed望んでいた方法で名前をいじった小さなスクリプトを書いたりperl、すべてを処理する小さなスクリプトを書いたりしました。答えた他の人の一人がそのアプローチを取った。

もう1つ指摘したいのは、引用の使い方です。シェルスクリプトで何かをするたびに、引用には細心の注意を払う必要があることを思い出させます。シェルスクリプトの問題の大きな原因は、シェルが想定外のことを解釈することです。そして、引用規則は明白からほど遠いです。このシェルスクリプトには引用の問題がないと思います。


これは確かにうまく機能します:)シェル展開についても説明してくれてありがとう。
bsod99

ドット(。)を(_)で置き換えることに加えて、ファイル名のスペースを削除するにはどうすればよいですか。
弟子

これはとても良いスクリプトです。私が見ることができる唯一の改善は、それをディレクトリツリーを再帰的にして、$ 1を$ 2に置き換えることですが、それらはマイナーです。(または、私の先生が「生徒の練習として残した」と言っていたように!)
lbutlr 2017年

4

使用しますfor thisfile in *.*.*(つまり、名前にドットが2つ以上あるファイルをループします)。変数を引用し、次のよう--にオプションの終わりを示すために使用することを忘れないでくださいmv -- "$thisfile" "$newname.$extension"

zshを使用。

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

私はあなたの提案を間違って実装したかもしれませんが、これはmv-になります。。* _。*
bsod99

3

これはどう:

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

免責事項:最初にダミーディレクトリで試してください。まだテストしていません。


簡潔、簡潔、ほぼ書き込み専用です!イェーイ!笑う
12

書き込み専用?Perlがそのように呼ばれたのはそれが初めて思います!もう一度笑う
ジョセフR.

そこで読みやすさを向上させるための試みを行いました。これが「書き込み専用」ではないことを願っています:)
Joseph R.

1
はい、それはたくさんを助けます。プレースホルダーとして使用できる唯一の妥当な文字があるのは残念です/
全知

2

いくつかの良い答えがすでに利用可能であるように見えますが、ここに別の使用方法がtrありsedます:

#!/bin/bash

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

どのように最大のムンクを適用sedするかを決めるの.*ですか?2番目.*があったら、私はそれについてずっと気分が良くなるでしょう[^_]*
12

これは、「拡張子なし」の場合と\t文字が含まれるファイル名の場合にも当てはまると思います。
全知

これは間違いなく簡単な解決策でした。ご入力いただきありがとうございます...最初の推奨事項に対応するために正規表現を修正しました。あなたが言った他の状況を説明するためにそれを微調整できるかどうかを確認します。
JC Yamokoski

echo -n "$file"うまくいくでしょう。:-)ああ、そして式(別名)"の周りにあり$( ... )ます"$( ... )"
全知

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.