Ganti titik-titik dengan garis bawah pada nama file, biarkan ekstensi tetap utuh

8

Saya memiliki skrip bash yang saya coba untuk mengganti titik-titik dalam nama file dan menggantinya dengan garis bawah, membiarkan ekstensi tetap utuh (saya berada di Centos 6 btw). Seperti yang Anda lihat dari output di bawah ini, skrip berfungsi ketika ada titik untuk diganti, tetapi dalam kasus di mana satu-satunya titik adalah ekstensi, skrip tetap mencoba untuk mengubah nama file, alih-alih mengabaikannya. Adakah yang bisa menunjukkan bagaimana saya harus menangani ini dengan lebih baik? Terima kasih atas bantuannya.

Skrip (salah) saya:

#!/bin/bash

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

Input sampel:

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

Keluaran:

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
bsod99
sumber
1
Bagaimana dengan kasus rumit seperti tar.gzfile? Anda ingin mereka menyelesaikannya file.tar.gz, bukan file_tar.gz.
IQAndreas

Jawaban:

10

Saya percaya bahwa program ini akan melakukan apa yang Anda inginkan. Saya telah mengujinya dan berfungsi pada beberapa kasus menarik (seperti tidak ada ekstensi sama sekali):

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

Masalah utama yang Anda miliki adalah bahwa ##ekspansi tidak melakukan apa yang Anda inginkan. Saya selalu menganggap ekspansi parameter shell di bash sebagai sesuatu dari seni hitam. Penjelasan dalam manual ini tidak sepenuhnya jelas, dan mereka tidak memiliki contoh pendukung tentang bagaimana ekspansi seharusnya bekerja. Mereka juga agak samar.

Secara pribadi, saya akan telah menulis sebuah skrip kecil sedyang mengotak-atik nama seperti yang saya inginkan, atau menulis skrip kecil perlyang hanya melakukan semuanya. Salah satu orang yang menjawab mengambil pendekatan itu.

Satu hal lain yang ingin saya tunjukkan adalah penggunaan kutipan saya. Setiap kali saya melakukan apa pun dengan skrip shell, saya mengingatkan orang untuk sangat berhati-hati dengan kutipan mereka. Sumber besar masalah dalam skrip shell adalah shell menafsirkan hal-hal yang tidak seharusnya. Dan aturan mengutipnya jauh dari jelas. Saya percaya skrip shell ini bebas dari mengutip masalah.

Beraneka ragam
sumber
Ini memang melakukan pekerjaan dengan baik :) Terima kasih atas penjelasannya juga kembali ekspansi shell.
bsod99
Selain mengganti titik (.) Dengan (_), bagaimana saya bisa menghapus spasi di nama file.
Disiplin
Ini skrip yang sangat bagus. Satu-satunya perbaikan yang dapat saya lihat adalah membuatnya mengulang pohon direktori dan membiarkan Anda mengganti $ 1 dengan $ 2, tetapi itu kecil. (Atau, seperti yang sering dikatakan oleh guru saya, "dibiarkan sebagai latihan untuk siswa"!)
lbutlr
4

Gunakan for thisfile in *.*.*(yaitu, loop file dengan dua titik atau lebih dalam namanya). Ingatlah untuk mengutip variabel Anda dan gunakan --untuk menandai akhir opsi seperti padamv -- "$thisfile" "$newname.$extension"

Dengan zsh.

autoload -U zmv
zmv '(*).(*)' '${1//./_}.$2'
Stéphane Chazelas
sumber
Saya mungkin telah menerapkan saran Anda dengan salah, tetapi ini menghasilkan mv - . . * _ . *
bsod99
3

Bagaimana dengan ini:

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

PENOLAKAN: coba dulu pada direktori dummy, saya belum mengujinya!

Joseph R.
sumber
Terse, bernas, hampir hanya menulis! Yay perl! terkekeh
Mahakuasa
Hanya menulis? Saya yakin itu pertama kalinya Perl disebut demikian! ( Terkekeh lagi )
Joseph R.
Mengambil kesempatan untuk meningkatkan keterbacaan di sana. Semoga ini terasa kurang "hanya untuk menulis" :)
Joseph R.
1
Ya, itu banyak membantu. Sangat disayangkan satu-satunya karakter yang masuk akal untuk digunakan sebagai pengganti ada /.
Mahakuasa
2

Sepertinya beberapa jawaban yang baik sudah tersedia, tetapi berikut ini menggunakan trdan sed:

#!/bin/bash

for file in *; do
    newname=$(echo $file | tr '.' '_' | sed 's/\(.*\)_\([^_]*\)$/\1.\2/g')
    [ "$newname" != "$file" ] && mv "$file" "$newname"
done
JC Yamokoski
sumber
Bagaimana cara sedmemutuskan mana yang .*akan digunakan untuk mengunyah maksimal? Saya akan merasa jauh lebih baik tentang hal itu jika kedua .*adalah [^_]*.
Mahakuasa
Saya juga percaya ini jatuh pada kasus 'no extension' dan nama file dengan \tkarakter di dalamnya.
Mahakuasa
Ini jelas hanya solusi cepat. Terima kasih atas masukan Anda ... Saya telah memperbaiki regex untuk mengakomodasi rekomendasi pertama Anda. Saya akan melihat apakah saya dapat mengubahnya untuk memperhitungkan keadaan lain yang Anda sebutkan.
JC Yamokoski
echo -n "$file"akan bekerja. :-) Oh, dan "sekitar $( ... )ekspresi (alias "$( ... )") akan melakukannya.
Mahakuasa
1

Versi ini memungkinkan Anda secara eksplisit memilih jumlah titik yang ingin Anda pertahankan, mulai dari sisi kanan.

Itu juga akan mengganti dan / atau menghapus karakter lain selain titik-titik, dan karakter pengganti -bukan garis bawah, tetapi ini dapat dengan mudah diubah.

#!/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
Ernest A
sumber