Thay thế dấu chấm bằng dấu gạch dưới trong tên tệp, giữ nguyên phần mở rộng


8

Tôi có một tập lệnh bash mà tôi đang cố gắng thay thế các dấu chấm trong tên tệp và thay thế chúng bằng dấu gạch dưới, giữ nguyên phần mở rộng (Tôi đang ở trên Centos 6 btw). Như bạn có thể thấy từ đầu ra bên dưới, tập lệnh hoạt động khi có một dấu chấm để thay thế, nhưng trong trường hợp dấu chấm duy nhất là phần mở rộng, tập lệnh vẫn cố gắng đổi tên tệp, thay vì bỏ qua nó. Bất cứ ai có thể chỉ ra làm thế nào tôi nên xử lý này tốt hơn? Cảm ơn vì bất kì sự giúp đỡ.

Kịch bản (bị lỗi) của tôi:

#!/bin/bash

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

Đầu vào mẫu:

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

Đầu ra:

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
Điều gì về các trường hợp khó khăn như tar.gzcác tập tin? Bạn sẽ muốn họ giải quyết file.tar.gz, không file_tar.gz.
IQAndreas

Câu trả lời:


10

Tôi tin rằng chương trình này sẽ làm những gì bạn muốn. Tôi đã thử nghiệm nó và nó hoạt động trên một số trường hợp thú vị (chẳng hạn như không có phần mở rộng nào):

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

Vấn đề chính bạn gặp phải là việc ##mở rộng không làm những gì bạn muốn. Tôi đã luôn coi việc mở rộng tham số shell trong bash là một thứ gì đó của nghệ thuật đen. Các giải thích trong hướng dẫn không hoàn toàn rõ ràng và chúng thiếu bất kỳ ví dụ hỗ trợ nào về cách mở rộng được cho là hoạt động. Chúng cũng khá khó hiểu.

Cá nhân, tôi đã viết một kịch bản nhỏ trong sedđó đánh vần tên theo cách tôi muốn, hoặc viết một kịch bản nhỏ trong perlđó đã làm toàn bộ. Một trong những người khác trả lời đã tiếp cận phương pháp đó.

Một điều khác tôi muốn chỉ ra là việc tôi sử dụng trích dẫn. Mỗi khi tôi làm bất cứ điều gì với shell script tôi đều nhắc nhở mọi người phải rất cẩn thận với trích dẫn của họ. Một nguồn lớn các vấn đề trong các kịch bản shell là shell diễn giải những thứ mà nó không được phép. Và các quy tắc trích dẫn là xa rõ ràng. Tôi tin rằng kịch bản shell này là miễn phí trích dẫn các vấn đề.


Điều này thực sự làm công việc độc đáo :) Cảm ơn bạn đã giải thích quá trình mở rộng vỏ.
bsod99

Ngoài việc thay thế dấu chấm (.) Bằng (_), làm cách nào tôi có thể xóa khoảng trắng trong tên tệp.
discipulus

Đây là một kịch bản rất hay. Những cải tiến duy nhất tôi có thể thấy là làm cho nó lặp lại một cây thư mục và cho phép bạn thay thế $ 1 bằng $ 2, nhưng đó là những thứ nhỏ. (Hoặc, như giáo viên của tôi thường nói, "còn lại như một bài tập cho học sinh"!)
lbutlr

4

Sử dụng for thisfile in *.*.*(nghĩa là lặp qua các tệp có hai dấu chấm trở lên trong tên của chúng). Hãy nhớ trích dẫn các biến của bạn và sử dụng --để đánh dấu sự kết thúc của các tùy chọn như trongmv -- "$thisfile" "$newname.$extension"

Với zsh.

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

Tôi có thể đã thực hiện sai đề xuất của bạn, nhưng điều này dẫn đến mv - . . * _ . *
bsod99

3

Còn cái này thì sao:

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

TUYÊN BỐ TỪ CHỐI: trước tiên hãy thử nó trên một thư mục giả, tôi chưa thử nó!


Terse, pithy, gần như chỉ viết! Yay perl! cười thầm
Omnifarious 26/11 '

Chỉ viết? Tôi cá rằng đó là lần đầu tiên Perl được gọi như vậy! ( cười lại lần nữa )
Joseph R.

Đã thực hiện một cải thiện khả năng đọc ở đó. Hy vọng điều này cảm thấy bớt "chỉ viết" :)
Joseph R.

1
Vâng, điều đó giúp một bó. Thật đáng tiếc khi nhân vật hợp lý duy nhất được sử dụng như một người giữ chỗ ở đó /.
Omnifarious

2

Có vẻ như một số câu trả lời tốt đã có sẵn, nhưng đây là một câu trả lời khác sử dụng trsed:

#!/bin/bash

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

Làm thế nào để sedquyết định .*áp dụng munch tối đa cho? Tôi sẽ cảm thấy tốt hơn rất nhiều về nó nếu thứ hai .*[^_]*.
Omnifarious 26/11 '

Tôi cũng tin rằng điều này rơi vào trường hợp 'không có phần mở rộng' và tên tệp có các \tký tự trong đó.
Omnifarious 26/11 '

Đây chắc chắn chỉ là một giải pháp nhanh chóng. Cảm ơn cho đầu vào của bạn ... Tôi đã sửa regex để phù hợp với đề xuất đầu tiên của bạn. Tôi sẽ xem liệu tôi có thể điều chỉnh nó để giải thích cho các trường hợp khác mà bạn đã đề cập không.
JC Yamokoski

echo -n "$file"sẽ làm việc :-) Oh, và "xung quanh $( ... )biểu thức (aka "$( ... )") sẽ làm điều đó.
Omnifarious 26/11 '

1

Phiên bản này cho phép bạn chọn rõ ràng số lượng chấm bạn muốn giữ, bắt đầu từ phía bên tay phải.

Ngoài ra, nó sẽ thay thế và / hoặc xóa các ký tự khác ngoài dấu chấm, và ký tự -thay thế thay vì dấu gạch dưới, nhưng điều này có thể dễ dàng thay đổi.

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.