Lấy phần mở rộng trong tên tệp


33

Làm cách nào để có được phần mở rộng tập tin từ bash? Đây là những gì tôi đã thử:

filename=`basename $filepath`
fileext=${filename##*.}

Bằng cách đó, tôi có thể mở rộng bz2từ đường dẫn /dir/subdir/file.bz2, nhưng tôi gặp vấn đề với đường dẫn /dir/subdir/file-1.0.tar.bz2.

Tôi muốn một giải pháp chỉ sử dụng bash mà không có chương trình bên ngoài nếu có thể.

Để làm rõ câu hỏi của tôi, tôi đã tạo một tập lệnh bash để trích xuất bất kỳ kho lưu trữ đã cho nào chỉ bằng một lệnh duy nhất extract path_to_file. Làm thế nào để trích xuất các tập tin được xác định bởi kịch bản bằng cách nhìn thấy nén hoặc lưu trữ loại, đó có thể là .tar.gz, .gz, .bz2 vv Tôi nghĩ rằng điều này sẽ liên quan đến chuỗi thao tác, ví dụ nếu tôi nhận được phần mở rộng .gzsau đó tôi nên kiểm tra xem nó có chuỗi .tartrước hay không .gz- nếu vậy, phần mở rộng sẽ là .tar.gz.


2
file = "/ dir / subir / file-1.0.tar.bz2"; echo $ {file ## *.} in '.bz2' tại đây. Đầu ra mà bạn đang mong đợi là gì?
axel_c

1
tôi cần.tar.bz2
uray

Câu trả lời:


19

Nếu tên tập tin là file-1.0.tar.bz2, phần mở rộng là bz2. Phương thức bạn đang sử dụng để trích xuất phần mở rộng ( fileext=${filename##*.}) là hoàn toàn hợp lệ¹.

Làm thế nào để bạn quyết định rằng bạn muốn mở rộng được tar.bz2và không bz2hay 0.tar.bz2? Bạn cần trả lời câu hỏi này trước. Sau đó, bạn có thể tìm ra lệnh shell nào phù hợp với đặc điểm kỹ thuật của bạn.

  • Một đặc điểm kỹ thuật có thể là các phần mở rộng phải bắt đầu bằng một chữ cái. Heuristic này thất bại đối với một vài phần mở rộng phổ biến như 7z, có thể được coi là trường hợp đặc biệt. Đây là một triển khai bash / ksh / zsh:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}
    

    Đối với tính di động POSIX, bạn cần sử dụng casecâu lệnh để khớp mẫu.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
    
  • Một đặc điểm kỹ thuật khác có thể là một số phần mở rộng biểu thị mã hóa và chỉ ra rằng cần phải tước thêm. Đây là một triển khai bash / ksh / zsh (yêu cầu shopt -s extglobtheo bash và setopt ksh_globdưới zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}
    

    Lưu ý rằng điều này được coi 0là một phần mở rộng trong file-1.0.gz.

¹ và các cấu trúc liên quan là trong POSIX , vì vậy họ làm việc trong bất kỳ shell Bourne kiểu phi cổ như tro, bash, ksh hoặc zsh. ${VARIABLE##SUFFIX}


điều đó sẽ được giải quyết, bằng cách kiểm tra xem chuỗi trước .mã thông báo cuối cùng có phải là loại lưu trữ hay không, ví dụ tar, nếu loại không lưu trữ như 0lặp lại sẽ kết thúc.
uray

2
@uray: hoạt động trong trường hợp cụ thể này, nhưng nó không phải là một giải pháp chung. Hãy xem xét ví dụ của Maciej.patch.lzma . Một heuristic, tốt hơn là nên xem xét các chuỗi sau khi người cuối cùng .: nếu đó là một hậu tố nén ( .7z, .bz2, .gz, ...), tiếp tục tước.
Gilles 'SO- ngừng trở nên xấu xa'

@NoamM Điều gì đã xảy ra với vết lõm? Nó chắc chắn bị hỏng sau khi chỉnh sửa của bạn: mã lồng đôi được thụt vào giống như mã lồng nhau.
Gilles 'SO- ngừng trở nên xấu xa'

22

Bạn có thể đơn giản hóa các vấn đề bằng cách chỉ thực hiện khớp mẫu trên tên tệp thay vì trích xuất phần mở rộng hai lần:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac

Giải pháp này rất đơn giản.
AsymLabs


2

Đây là cú đánh của tôi vào nó: Dịch các dấu chấm sang dòng mới, chuyển qua tail, lấy dòng cuối cùng:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678

0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Ví dụ:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma

Không hoạt động cho tất cả các trường hợp. Hãy thử với 'foo.7z'
axel_c

Bạn cần dấu ngoặc kép và sử dụng tốt hơn printftrong trường hợp tên tệp chứa dấu gạch chéo ngược hoặc bắt đầu bằng -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- ngừng trở thành ác quỷ'

@axel_c: đúng và tôi đã triển khai thông số kỹ thuật tương tự như Maciej làm ví dụ. Những gì heuristic bạn đề nghị là tốt hơn so với bắt đầu bằng một chữ cái?
Gilles 'SO- ngừng trở nên xấu xa'

1
@Gilles: Tôi chỉ nghĩ rằng không có giải pháp nào trừ khi bạn sử dụng danh sách các tiện ích mở rộng đã biết trước, bởi vì tiện ích mở rộng có thể là bất cứ thứ gì.
axel_c

0

Một ngày nọ tôi đã tạo ra các chức năng khó khăn đó:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

Tôi đã tìm thấy cách tiếp cận đơn giản này, rất hữu ích trong nhiều trường hợp, không chỉ khi nó đi về các tiện ích mở rộng.

Để kiểm tra tiện ích mở rộng - Thật đơn giản và đáng tin cậy

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

Đối với phần mở rộng cắt:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

Để thay đổi phần mở rộng:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

Hoặc, nếu bạn thích "các chức năng tiện dụng:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

PS Nếu bạn thích các chức năng đó hoặc thấy chúng được sử dụng đầy đủ, vui lòng tham khảo bài đăng này :) (và hy vọng đưa ra nhận xét).


0

câu trả lời dựa trên trường hợp jackman là khá tốt và dễ mang theo, nhưng nếu bạn chỉ muốn tên tệp và phần mở rộng trong một biến tôi đã tìm thấy giải pháp này:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

Nó chỉ hoạt động với hai phần mở rộng và cái đầu tiên phải là "tar".

Nhưng bạn có thể thay đổi dòng kiểm tra "tar" bằng kiểm tra độ dài chuỗi và lặp lại sửa chữa nhiều lần.


-1

tôi đã giải quyết nó bằng cách này:

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

nhưng điều này chỉ hoạt động cho kiểu lưu trữ đã biết, trong trường hợp này chỉ tar

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.