การคว้าส่วนขยายในชื่อไฟล์


33

ฉันจะรับนามสกุลไฟล์จาก bash ได้อย่างไร นี่คือสิ่งที่ฉันพยายาม:

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

โดยการทำที่ฉันจะได้รับการขยายตัวของbz2จากเส้นทางแต่ฉันมีปัญหากับเส้นทาง/dir/subdir/file.bz2/dir/subdir/file-1.0.tar.bz2

ฉันต้องการโซลูชันที่ใช้ bash เท่านั้นโดยไม่มีโปรแกรมภายนอกถ้าเป็นไปได้

extract path_to_fileเพื่อให้คำถามของฉันล้างผมก็สร้างสคริปต์ทุบตีเพื่อแยกเก็บใดก็ตามเพียงแค่คำสั่งเดียวของ วิธีการแตกไฟล์จะถูกกำหนดโดยสคริปต์โดยดูประเภทการบีบอัดหรือการเก็บถาวรซึ่งอาจเป็น. tar.gz, .gz, .bz2 เป็นต้นฉันคิดว่านี่ควรเกี่ยวข้องกับการจัดการสตริงตัวอย่างเช่นถ้าฉันได้รับการขยาย.gzแล้ว ควรตรวจสอบว่ามันมีสตริง.tarก่อน.gz- .tar.gzถ้าเป็นเช่นนั้นส่วนขยายที่ควรจะเป็น


2
ไฟล์ = "/ dir / subdir / ไฟล์ 1.0.tar.bz2"; echo $ {file ## *.} พิมพ์ '.bz2' ที่นี่ ผลลัพธ์ที่คุณคาดหวังคืออะไร
axel_c

1
ฉันต้องการ.tar.bz2
uray

ที่เกี่ยวข้อง: สารสกัดจากชื่อไฟล์และนามสกุลในทุบตี
kenorb

คำตอบ:


19

ถ้าชื่อไฟล์เป็นนามสกุลเป็นfile-1.0.tar.bz2 bz2วิธีที่คุณใช้ในการขยายส่วนขยาย ( fileext=${filename##*.}) นั้นใช้ได้อย่างสมบูรณ์¹

วิธีทำคุณตัดสินใจว่าคุณต้องการขยายให้เป็นtar.bz2และไม่ได้bz2หรือ0.tar.bz2? คุณต้องตอบคำถามนี้ก่อน จากนั้นคุณสามารถหาคำสั่งเชลล์ที่ตรงกับข้อกำหนดของคุณ

  • หนึ่งในข้อกำหนดที่เป็นไปได้คือส่วนขยายจะต้องเริ่มต้นด้วยตัวอักษร ฮิวริสติกนี้ล้มเหลวสำหรับส่วนขยายทั่วไปบางอย่างเช่น7zซึ่งอาจถือว่าเป็นกรณีพิเศษที่สุด นี่คือการใช้ bash / ksh / zsh:

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

    สำหรับการพกพา POSIX คุณต้องใช้caseคำสั่งสำหรับการจับคู่รูปแบบ

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • ข้อมูลจำเพาะที่เป็นไปได้อีกอย่างหนึ่งคือส่วนขยายบางส่วนแสดงถึงการเข้ารหัสและระบุว่าจำเป็นต้องมีการลอกเพิ่มเติม ต่อไปนี้เป็นการนำ bash / ksh / zsh (ต้องการshopt -s extglobภายใต้ bash และsetopt ksh_globภายใต้ 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%.}

    โปรดทราบว่านี้จะพิจารณาเพื่อเป็นส่วนขยายใน0file-1.0.gz

¹ และโครงสร้างที่เกี่ยวข้องอยู่ในPOSIXดังนั้นจึงทำงานในเชลล์สไตล์ Bourne ที่ไม่ใช่ของโบราณเช่น ash, bash, ksh หรือ zsh ${VARIABLE##SUFFIX}


ที่ควรได้รับการแก้ไขโดยตรวจสอบว่าสตริงก่อน.โทเค็นล่าสุดเป็นประเภทไฟล์เก็บถาวรหรือไม่ตัวอย่างเช่นtarหากไฟล์นั้นไม่ใช่ประเภทไฟล์เก็บถาวรเช่น0การวนซ้ำควรสิ้นสุด
uray

2
@uray: ใช้งานได้ในกรณีนี้โดยเฉพาะ แต่ไม่ใช่วิธีแก้ไขปัญหาทั่วไป พิจารณาตัวอย่าง Maciej .patch.lzmaของ แก้ปัญหาที่ดีกว่าจะต้องพิจารณาสตริงหลังจากล่าสุด.: ถ้าเป็นคำต่อท้ายการบีบอัด ( .7z, .bz2, .gz, ... ) ให้ดำเนินการต่อการปอก
Gilles 'หยุดความชั่วร้าย'

@Nomem มีอะไรผิดปกติกับการเยื้อง? มันจะแตกหักอย่างแน่นอนหลังจากการแก้ไขของคุณ: รหัสซ้อนกันสองครั้งจะถูกเยื้องเช่นเดียวกับซ้อนกันโดยลำพัง
Gilles 'หยุดความชั่วร้าย' ใน

22

คุณอาจทำให้เรื่องต่างๆง่ายขึ้นโดยเพียงทำการจับคู่รูปแบบกับชื่อไฟล์แทนการแยกส่วนขยายออกสองครั้ง

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

วิธีนี้ง่ายมาก
AsymLabs

6
$ echo "thisfile.txt"|awk -F . '{print $NF}'

ความคิดเห็นเกี่ยวกับเรื่องนี้ที่นี่: http://liquidat.wordpress.com/2007/09/29/short-tip-get-file-extension-in-shell-script/


1
ใช้งานไม่ได้สำหรับการ.tar.gzขยาย
uray

4
จริง ๆ แล้ว. tar.gz เป็นน้ำมันดินในไฟล์ gzip ดังนั้นมันจึงใช้งานได้ในแง่ที่ว่ามันจะลบนามสกุล gz ออกจากไฟล์ gzip
Chris

2

นี่คือช็อตของฉันที่: แปลชี้ไปที่บรรทัดใหม่, ไพพ์tailไลน์, รับบรรทัดสุดท้าย:

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

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

ตัวอย่างเช่น:

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

ไม่สามารถใช้ได้กับทุกกรณี ลองด้วย 'foo.7z'
axel_c

คุณจำเป็นต้องมีเครื่องหมายคำพูดและใช้งานได้ดีขึ้นprintfในกรณีที่ชื่อไฟล์มีเครื่องหมายแบ็กสแลชหรือเริ่มต้นด้วย-:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- หยุดความชั่วร้าย'

@axel_c: ถูกต้องและฉันได้ใช้ข้อมูลจำเพาะเช่นเดียวกับ Maciej เป็นตัวอย่าง ฮิวริสติกแบบใดที่คุณแนะนำว่าดีกว่า“ เริ่มด้วยตัวอักษร”?
Gilles 'หยุดความชั่วร้าย'

1
@Gilles: ฉันแค่คิดว่าไม่มีวิธีแก้ปัญหาเว้นแต่ว่าคุณจะใช้รายการส่วนขยายที่รู้จักที่ถูกคำนวณไว้ล่วงหน้าเนื่องจากส่วนขยายสามารถเป็นอะไรก็ได้
axel_c

0

วันหนึ่งฉันได้สร้างฟังก์ชั่นที่ยุ่งยากเหล่านั้น:

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

ฉันพบวิธีการตรงไปตรงมานี้มีประโยชน์มากในหลายกรณีไม่เพียง แต่เมื่อมันเกี่ยวกับส่วนขยาย

สำหรับการตรวจสอบส่วนขยาย - มันง่ายและเชื่อถือได้

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

สำหรับส่วนขยายการตัด:

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

สำหรับการเปลี่ยนนามสกุล:

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

หรือถ้าคุณชอบฟังก์ชั่นที่ใช้งานง่าย:

~$ 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

ป.ล. ถ้าคุณชอบฟังก์ชั่นเหล่านั้นหรือพบว่าพวกเขาใช้เต็มโปรดดูที่โพสต์นี้ :) (และหวังว่าจะใส่ความคิดเห็น)


0

คำตอบกรณีแจ็คแมนค่อนข้างดีและพกพาได้ แต่ถ้าคุณต้องการชื่อไฟล์และส่วนขยายในตัวแปรฉันพบวิธีนี้แล้ว:

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

ใช้งานได้กับส่วนขยายสองเท่าและส่วนแรกต้องเป็น "tar"

แต่คุณสามารถเปลี่ยนบรรทัดการทดสอบ "tar" ด้วยการทดสอบความยาวสตริงและทำการแก้ไขซ้ำหลายครั้ง


-1

ฉันแก้ไขมันโดยใช้สิ่งนี้:

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

แต่จะใช้ได้กับประเภทการเก็บถาวรที่รู้จักเท่านั้นในกรณีนี้เท่านั้น tar

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.