Bash'de dosya adını ve uzantıyı ayıklayın


2107

Dosya adını (uzantısız) ve uzantıyı ayrı ayrı almak istiyorum.

Şimdiye kadar bulduğum en iyi çözüm:

NAME=`echo "$FILE" | cut -d'.' -f1`
EXTENSION=`echo "$FILE" | cut -d'.' -f2`

Bu yanlıştır çünkü dosya adı birden fazla .karakter içeriyorsa çalışmaz . Diyelim ki, varsa a.b.js, ave b.jsyerine, a.bve dikkate alacaktır js.

Python ile kolayca yapılabilir.

file, ext = os.path.splitext(path)

ancak mümkünse bunun için bir Python tercümanı başlatmamayı tercih ederim.

Daha iyi bir fikrin var mı?


Bu soru , bu bash tekniğini ve diğer ilgili teknikleri açıklamaktadır.
jjclarkson

28
Aşağıdaki harika cevapları uygularken, değişkeninizi buraya gösterdiğim gibi yapıştırmayın Yanlış: extension="{$filename##*.}" bir süredir yaptığım gibi! $Kıvrımların dışını hareket ettirin : Sağ: extension="${filename##*.}"
Chris K

4
Bu açıkça önemsiz bir sorundur ve benim için aşağıdaki cevapların tamamen doğru olup olmadığını söylemek zor. Bu, (ba) sh'de yerleşik bir işlem değildir (cevaplar, desen eşleşmesini kullanarak işlevi uyguluyor gibi görünüyor) şaşırtıcı. Bunun os.path.splitextyerine yukarıdaki
Peter Gibson

1
As uzantısı temsil etmek zorunda doğayı bir dosyanın, bir var sihirli onun doğasını ve offert tahmin etmeye dosyasını kontrol komutu standart uzantısı . bkz Cevabımı
F. Hauri

2
Soru ilk etapta sorunlu çünkü OS ve unix dosya sistemleri açısından bakıldığında dosya uzantısı diye bir şey yok. "." parçaları ayırmak , sadece insanlar onu takip etmeyi kabul ettiği sürece çalışan bir insan sözleşmesidir . Örneğin, 'tar' programı ile çıktı dosyalarını "tar" olarak adlandırmaya karar verilmiş olabilir. ".tar" soneki yerine önek - "somedir.tar" yerine "tar.somedir" verilmesi. Bu nedenle "genel, her zaman işe yarar" çözümü yoktur - özel gereksinimlerinize ve beklenen dosya adlarına uyan bir kod yazmanız gerekir.
CM

Yanıtlar:


3499

İlk olarak, dosya adını yol olmadan alın:

filename=$(basename -- "$fullfile")
extension="${filename##*.}"
filename="${filename%.*}"

Alternatif olarak, '.' Yerine yolun son '/' öğesine odaklanabilirsiniz. öngörülemeyen dosya uzantılarınız olsa bile çalışmalıdır:

filename="${fullfile##*/}"

Belgeleri kontrol etmek isteyebilirsiniz:


85
Check out gnu.org/software/bash/manual/html_node/... tam bir özellik seti.
D.Shawley

24
"$ Fullfile" dosyasına bazı alıntılar ekleyin, aksi takdirde dosya adını bozma riskiniz olacaktır.
lhunath

47
Heck, yapabilirsin hatta yazma dosya adı = "$ {fullfile ## * /}" ve önlemek ekstra çağırarakbasename
ephemient

45
Dosyanın bir uzantısı yoksa bu "çözüm" çalışmaz - bunun yerine, tüm dosya adı çıktıdır, bu da uzantısız dosyaların her yerde mevcut olduğu göz önüne alındığında oldukça kötüdür.
nccc

43
Uzantısı olmadan dosya adları ile uğraşan için düzeltme: extension=$([[ "$filename" = *.* ]] && echo ".${filename##*.}" || echo ''). Uzatma eğer Not olan mevcut başlangıçtaki dahil olmak üzere döndürülür ., örneğin .txt.
mklement0

684
~% FILE="example.tar.gz"

~% echo "${FILE%%.*}"
example

~% echo "${FILE%.*}"
example.tar

~% echo "${FILE#*.}"
tar.gz

~% echo "${FILE##*.}"
gz

Daha fazla ayrıntı için Bash kılavuzundaki kabuk parametresi genişletmesine bakın .


22
Siz (belki istemeden) .tar.gz dosyasında olduğu gibi dosya adının "uzantı" kısmında 2 nokta varsa ne yapacağınız konusunda mükemmel bir soru ortaya çıkarırsınız. önündeki tüm geçerli dosya uzantılarını bilmeden çözülemez.
rmeador

8
Neden çözülemiyor? Örneğimde, dosyanın iki noktalı bir uzantı değil , iki uzantı içerdiği düşünülmelidir . Her iki uzantıyı da ayrı ayrı ele alırsınız.
Juliano

22
Sözcüksel olarak çözülemez, dosya türünü kontrol etmeniz gerekir. dinosaurs.in.tardinosaurs.in.tar.gz
Adlı

11
Tam yollardan geçiyorsanız bu daha karmaşık hale gelir. İçlerinden birinde '.' Vardı. yolun ortasındaki bir dizinde, ancak dosya adında yok. Örnek "a / bc / d / e / dosyaadı" ".c / d / e / dosyaadı"
Walt Sellers

6
açıkça hiçbir x.tar.gzuzantısı gzve dosya adı budur x.tar. İkili uzantı diye bir şey yoktur. Ben eminim boost :: dosya sistemi bu şekilde işler. (bölünmüş yol, değişiklik_uzantısı ...) ve davranışı yanılmıyorsam python'a dayanır.
v.oddou

430

Genellikle uzantıyı zaten biliyorsunuzdur, bu nedenle kullanmak isteyebilirsiniz:

basename filename .extension

Örneğin:

basename /path/to/dir/filename.txt .txt

ve alırız

filename

60
Bu ikinci argüman basenametamamen göz açıcı, ty kind efendim / bayan :)
akaIDIOT

10
Ve bu tekniği kullanarak uzantı nasıl çıkarılır? ;) Bekle! Aslında bunu önceden bilmiyoruz.
Tomasz Gandor

3
Diyelim ki .zipveya ile biten sıkıştırılmış bir dizininiz var .ZIP. Böyle bir şey yapmanın bir yolu var mı basename $file {.zip,.ZIP}?
Dennis

8
Bu, OP sorununun yalnızca bir kısmını yanıtlarken, Google'a yazdığım soruyu cevaplıyor. :-) Çok kaygan!
sudo make install

1
kolay ve POSIX uyumlu
gpanda

146

POSIX parametre genişletmesinin büyüsünü kullanabilirsiniz:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo "${FILENAME%%.*}"
somefile
bash-3.2$ echo "${FILENAME%.*}"
somefile.tar

Dosyanızın adı formunun olsaydı bunda bir uyarı var ./somefile.tar.gzsonra echo ${FILENAME%%.*}iştahla en uzun maçı kaldıracak .ve boş bir dize olurdu.

(Geçici bir değişkenle bunun üzerinde çalışabilirsiniz:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)


Bu site daha fazlasını açıklıyor.

${variable%pattern}
  Trim the shortest match from the end
${variable##pattern}
  Trim the longest match from the beginning
${variable%%pattern}
  Trim the longest match from the end
${variable#pattern}
  Trim the shortest match from the beginning

5
Joachim'in cevabından çok daha basit ama her zaman POSIX değişken ikamesi aramak zorundayım. Ayrıca Max OSX bu ishal nerede cutyok --complementve sedyok -r.
jwadsack

72

Dosyanın uzantısı veya dosya adı yoksa bu işe yaramaz. İşte kullandığım; yalnızca yerleşikleri kullanır ve daha fazla (ancak hepsi değil) patolojik dosya isimlerini işler.

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

İşte bazı test kılıfları:

$ basename-and-extension.sh / / home / ben / / home / ben / dosya /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ...
/:
    dir = "/"
    base = ""
    ext = ""
/ Home / me /:
    dir = "/ home / me /"
    base = ""
    ext = ""
/ Home / me / dosya:
    dir = "/ home / me /"
    base = "dosya"
    ext = ""
/home/me/file.tar:
    dir = "/ home / me /"
    base = "dosya"
    ext = "katran"
/home/me/file.tar.gz:
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ home / me /"
    base = ".hidden"
    ext = ""
/home/me/.hidden.tar:
    dir = "/ home / me /"
    base = ".hidden"
    ext = "katran"
/ Home / me / ..:
    dir = "/ home / me /"
    base = ".."
    ext = ""
.:
    dir = ""
    base = "."
    ext = ""

2
Bunun yerine dir="${fullpath:0:${#fullpath} - ${#filename}}"sık sık gördüm dir="${fullpath%$filename}". Yazmak daha kolay. Gerçek hız farkı veya gotchas olup olmadığından emin değilim.
dubiousjim

2
Bu hemen hemen her zaman yanlış olan #! / Bin / bash kullanır. Mümkünse #! / Bin / sh veya yoksa #! / Usr / bin / env bash tercih edin.
İyi İnsan

@İyi Kişi: Neredeyse her zaman nasıl yanlış olduğunu bilmiyorum: which bash-> /bin/bash; belki de senin dağıtımın?
vol7ron

2
@ vol7ron - birçok dağıtımda bash / usr / local / bin / bash içinde. OSX'te birçok kişi / opt / local / bin / bash içine güncellenmiş bir bash yükler. Böyle / bin / bash yanlış ve bulmak için env kullanmak gerekir. Daha da iyisi / bin / sh ve POSIX yapılarını kullanmaktır. Solaris dışında bu bir POSIX kabuğu.
İyi Kişi

2
@ GoodPerson ama bash ile daha rahatsanız, neden sh kullanıyorsunuz? Bu demek değildir ki, sh kullanabileceğiniz zaman neden Perl kullanıyorsunuz?
vol7ron

46

Kullanabilirsiniz basename.

Misal:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

Sen hep yürütme ancak eğer koparılıp, uzantılı basename sağlamak gerekiyor tarile -zo zaman uzatma olacağını biliyorum .tar.gz.

Bu istediğinizi yapmalıdır:

tar -zxvf $1
cd $(basename $1 .tar.gz)

2
Sanırım cd $(basename $1 .tar.gz).gz dosyaları için çalışıyor. Ama söz konusu sözArchive files have several extensions: tar.gz, tat.xz, tar.bz2
SS Hegde

Tomi Po da aynı şeyi 2 yıl önce yayınladı.
phil294

Merhaba Blauhirn, wauw bu eski bir soru. Tarihlerde bir şey olduğunu düşünüyorum. Soruyu sorulduktan kısa bir süre sonra ve orada sadece birkaç başka cevabın cevaplandığını hatırlıyorum. Soru başka bir soruyla birleştirilebilir mi, SO bunu yapıyor mu?
Bjarke Freund-Hansen

Evet doğru hatırlıyorum. Aslında bu soruyu stackoverflow.com/questions/14703318/… 'nin sorulduğu aynı günde cevapladım , 2 yıl sonra bu soru ile birleştirildi. Cevabım bu şekilde taşındığında, yinelenen bir cevap için suçlanamıyorum.
Bjarke Freund-Hansen

37
pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

iyi çalışır, böylece şunları kullanabilirsiniz:

pax> FILE=a.b.js
pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//')
pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//')
pax> echo $NAME
a.b
pax> echo $EXTENSION
js

Bu arada komutlar aşağıdaki gibi çalışır.

İçin komut NAMEyerine kullanılan bir "."karakter olmayan herhangi bir sayıda ardından "."hiçbir şey (yani, nihai her şeyi kaldırır, satırın sonuna kadar karakterlerin "."satırın sonuna kadar dahil). Bu temel olarak normal ifade hilesi kullanan açgözlü olmayan bir ikamedir.

Komut, EXTENSIONherhangi bir sayıda karakteri ve ardından "."satırın başında bir karakteri, hiçbir şey kullanmadan değiştirir (yani, satırın başından son noktaya kadar her şeyi kaldırır). Bu, varsayılan eylem olan açgözlü bir ikame.


Bu ara, ad ve uzantı için aynı yazdırılacağı gibi uzantısız dosyalar için. Bu yüzden sed 's,\.[^\.]*$,,'isim ve sed 's,.*\.,., ;t ;g'uzantı için kullanıyorum ( tipik komutla birlikte atipik testve getkomutları kullanır substitute).
hIpPy

32

Mellen bir blog yazısı üzerine yorum yazıyor:

Bash kullanarak ${file%.*}, uzantı olmadan dosya adını ${file##*.}almak ve uzantıyı tek başına almak da vardır. Yani,

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

Çıktılar:

filename: thisfile
extension: txt


29

Gerek ile rahatsız etmek awkya da sedhatta perlbu basit görev için. os.path.splitext()Yalnızca parametre genişletmelerini kullanan saf Bash ile uyumlu bir çözüm vardır.

Referans uygulaması

Aşağıdakilerin belgeleri os.path.splitext(path):

Yol adı yolunu bir çifte bölün , (root, ext)böylece ext boş veya bir nokta ile başlar ve en fazla bir nokta içerir. Taban adında önde gelen süreler yok sayılır; döner .root + ext == pathsplitext('.cshrc')('.cshrc', '')

Python kodu:

root, ext = os.path.splitext(path)

Bash Uygulaması

Liderlik dönemlerini onurlandırma

root="${path%.*}"
ext="${path#"$root"}"

Ön dönemleri yok saymak

root="${path#.}";root="${path%"$root"}${root%.*}"
ext="${path#"$root"}"

Testler

Aşağıda, her girdideki Python başvuru uygulamasıyla eşleşmesi gereken Baştaki dönemleri yoksayma uygulaması için test örnekleri verilmiştir .

|---------------|-----------|-------|
|path           |root       |ext    |
|---------------|-----------|-------|
|' .txt'        |' '        |'.txt' |
|' .txt.txt'    |' .txt'    |'.txt' |
|' txt'         |' txt'     |''     |
|'*.txt.txt'    |'*.txt'    |'.txt' |
|'.cshrc'       |'.cshrc'   |''     |
|'.txt'         |'.txt'     |''     |
|'?.txt.txt'    |'?.txt'    |'.txt' |
|'\n.txt.txt'   |'\n.txt'   |'.txt' |
|'\t.txt.txt'   |'\t.txt'   |'.txt' |
|'a b.txt.txt'  |'a b.txt'  |'.txt' |
|'a*b.txt.txt'  |'a*b.txt'  |'.txt' |
|'a?b.txt.txt'  |'a?b.txt'  |'.txt' |
|'a\nb.txt.txt' |'a\nb.txt' |'.txt' |
|'a\tb.txt.txt' |'a\tb.txt' |'.txt' |
|'txt'          |'txt'      |''     |
|'txt.pdf'      |'txt'      |'.pdf' |
|'txt.tar.gz'   |'txt.tar'  |'.gz'  |
|'txt.txt'      |'txt'      |'.txt' |
|---------------|-----------|-------|

Test sonuçları

Tüm testler geçti.


2
Hayır, temel dosya adı text.tar.gzolmalı textve uzantı olmalıdır.tar.gz
frederick99

2
@ frederick99 Dediğim gibi burada çözüm os.path.splitextPython uygulanması ile eşleşir . Bu uygulamanın muhtemelen tartışmalı girdiler için uygun olup olmadığı başka bir konudur.
Cyker

Pattern ( "$root") içindeki tırnaklar nasıl çalışır? İhmal edilirlerse ne olabilir? (Konuyla ilgili herhangi bir belge bulamadım.) Bu, dosya adlarını bunlarla *veya ?içinde nasıl ele alıyor ?
ymett

Tamam, beni tırnak, desen değişmez yani yapmak gösterileri test *ve ?özel değildir. Yani sorumun iki kısmı birbirlerine cevap veriyor. Bunun belgelenmediğini düzeltir miyim? Yoksa bu tekliflerin genel olarak glob genişlemesini devre dışı bıraktığı gerçeğinden anlaşılmalıdır mı?
ymett

Mükemmel cevap! Sadece kök hesaplamak için biraz daha basit bir varyant önereceğim: root="${path#?}";root="${path::1}${root%.*}"- sonra uzantıyı çıkarmak için aynı işlemi yapın.
Maëlan

26

cutSon iki uzantıyı ( ".tar.gz"parça) kaldırmak için komutu kullanabilirsiniz :

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

Clayton Hughes'un bir yorumda belirttiği gibi, bu sorudaki gerçek örnek için işe yaramayacaktır. Alternatif olarak sed, genişletilmiş düzenli ifadelerle kullanmayı öneririm, şöyle:

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

Son iki (alfa-sayısal) uzantıyı koşulsuz olarak kaldırarak çalışır.

[Anders Lindahl'ın yorumundan sonra tekrar güncellendi]


4
Bu yalnızca dosya adı / yol başka nokta içermiyorsa çalışır: echo "mpc-1.0.1.tar.gz" | kes -d '.' --complement -f2- "mpc-1" üretir (ayırma işleminden sonra yalnızca ilk 2 alan)
Clayton Hughes

@ClaytonHughes Haklısın ve daha iyi test etmeliydim. Başka bir çözüm eklendi.
Bazı programcı ahbap

Sed ifadeleri $, eşleşen uzantının dosya adının sonunda olup olmadığını kontrol etmek için kullanmalıdır . Aksi takdirde, benzer bir dosya adı i.like.tar.gz.files.tar.bz2beklenmedik sonuçlara neden olabilir.
Anders Lindahl

@AndersLindahl Uzantıların sedsırası zincir sırasının tersi ise yine de olacaktır . Hatta ile $sonunda bir dosya adı gibi mpc-1.0.1.tar.bz2.tar.gzhem kaldıracaktır .tar.gzsonra .tar.bz2.
Bazı programcı ahbap

$ echo "foo.tar.gz" | kes -d '.' -f2- OLMADAN - tamamlayıcı, $ echo "foo.tar.gz" dizesinin sonuna 2. bölünmüş öğeyi alır | kes -d '.' -f2- tar.gz
Gene Black

23

Aşağıda, awkyazılım paketleri için sürüm numaralarını çıkarmak gibi bazı gelişmiş kullanım durumları da dahil olmak üzere bazı alternatif öneriler (çoğunlukla içinde ) bulunmaktadır.

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

Tüm kullanım durumları, ara sonuçlara bağlı olmaksızın orijinal tam yolu girdi olarak kullanır.


20

Kabul edilen cevap tipik durumlarda iyi çalışır , ancak kenar durumlarda başarısız olur , yani:

  • Uzantısı olmayan dosya adları için (bu cevabın geri kalanında sonek olarak adlandırılır ), extension=${filename##*.}boş bir dize yerine giriş dosya adını döndürür.
  • extension=${filename##*.}.sözleşmenin aksine ilkini içermez .
    • Körü körüne ön ekler ., sonek olmadan dosya adları için işe yaramaz.
  • filename="${filename%.*}"Girdi dosya adı ile başlıyorsa .ve başka .karakter içermiyorsa (örneğin, .bash_profile) boş dize olacaktır - kuralın aksine.

---------

Bu nedenle, tüm uç durumları kapsayan sağlam bir çözümün karmaşıklığı bir işlev gerektirir - aşağıdaki tanımına bakın; o dönebilirsiniz tüm bir yolun bileşenlerini .

Örnek çağrı:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

Giriş yolundan sonraki argümanların serbestçe seçildiğine dikkat edin, konumsal değişken adları .
İlgilenmeyen değişkenlerden önce gelen değişkenleri atlamak için _(atma değişkenini kullanmak için $_) veya ''; örneğin, yalnızca dosya adı kökü ve uzantısını ayıklamak için kullanın splitPath '/etc/bash.bashrc' _ _ fnameroot extension.


# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

İşlevi uygulayan test kodu:

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

Beklenen çıktı - kenar durumlarına dikkat edin:

  • soneki olmayan bir dosya adı
  • bir dosya adı başlangıç .( değil kabul sonekin başlangıcı)
  • ile biten bir giriş yolu /(sondaki /yok sayılır)
  • yalnızca dosya adı olan bir girdi yolu ( .üst yol olarak döndürülür)
  • - önceden .düzeltilmiş simgeden daha fazlasına sahip bir dosya adı (yalnızca sonuncusu sonek olarak kabul edilir):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt

19

En küçük ve en basit çözüm (tek satırda):

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo

Bu işe yaramaz bir kullanımecho . Genel olarak, kabuğun , sonucu görüntülemeden önce boşlukta jetonlama ve joker karakter genişletmesi gerçekleştirmesi gerekmediği sürece echo $(command)daha iyi yazılır . Test: çıktısı nedir (ve gerçekten istediğiniz şeyse, gerçekten sadece istersiniz ). commandcommandecho $(echo '*')echo *
Üçlü

@triplee echoKomutu hiç kullanmadım . Sadece foo2. satırın sonucu olarak 3. satırda görünen sonucu göstermek için kullandım .
Ron

Ama basename "${file%.*}"aynısını yapardı; çıktısını yakalamak için bir komut ikamesi kullanıyorsunuz, yalnızca echoaynı çıktıya hemen. (Alıntı yapmadan sonuç nominal olarak farklıdır; ancak bu neredeyse hiç alakalı değildir, burada çok daha az bir özelliktir.)
üçlü

Ayrıca basename "$file" .txtparametre ikamesinin karmaşıklığını önler.
tripleee

1
@Ron Onu zamanımızı boşa harcamakla suçlamadan önce ilk yorumunu okuyun.
frederick99

14

Sadece dosyanın adına ihtiyacınız varsa, bunu deneyebilirsiniz:

FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf

# Remove all the prefix until the "/" character
FILENAME=${FULLPATH##*/}

# Remove all the prefix until the "." character
FILEEXTENSION=${FILENAME##*.}

# Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file.
BASEDIRECTORY=${FULLPATH%$FILENAME}

echo "path = $FULLPATH"
echo "file name = $FILENAME"
echo "file extension = $FILEEXTENSION"
echo "base directory = $BASEDIRECTORY"

Ve hepsi = D.


Sadece BASEDIRECTORY istedim :) Teşekkürler!
Carlos Ricardo

12

-Alan numarasına eklenen tüm alanları ve sonraki alanları görüntülemeye zorlayabilirsiniz .

NAME=`basename "$FILE"`
EXTENSION=`echo "$NAME" | cut -d'.' -f2-`

Yani DOSYA ise eth0.pcap.gz , UZATMApcap.gz

Aynı mantığı kullanarak, aşağıdaki gibi kesim ile '-' kullanarak dosya adını da getirebilirsiniz:

NAME=`basename "$FILE" | cut -d'.' -f-1`

Bu, uzantısı olmayan dosya adları için bile geçerlidir.


8

Sihirli dosya tanıma

Bu Yığın Taşması sorusunda iyi cevapların yanı sıra eklemek istiyorum:

Linux ve diğer unixen altında, bazı ilk bayt dosyalarını analiz ederek dosya tipi algılama yapan sihirli bir komut vardır file. Bu çok eski bir araçtır, başlangıçta yazdırma sunucuları için kullanılır (bunun için oluşturulmamışsa ... Bundan emin değilim).

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

Standart uzantıları /etc/mime.types( Debian GNU / Linux masaüstümde bulunabilir. Bkz. man fileVe man mime.types. Belki fileyardımcı programı ve mime-supportpaketleri yüklemeniz gerekir ):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

Bir doğru uzantı belirleme işlevi. Küçük (mükemmel değil) bir örnek var:

file2ext() {
    local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype
    case ${_mimetype##*[/.-]} in
        gzip | bzip2 | xz | z )
            _mimetype=${_mimetype##*[/.-]}
            _mimetype=${_mimetype//ip}
            _basemimetype=$(file -zLb --mime-type "$1")
            ;;
        stream )
            _mimetype=($(file -Lb "$1"))
            [ "${_mimetype[1]}" = "compressed" ] &&
                _basemimetype=$(file -b --mime-type - < <(
                        ${_mimetype,,} -d <"$1")) ||
                _basemimetype=${_mimetype,,}
            _mimetype=${_mimetype,,}
            ;;
        executable )  _mimetype='' _basemimetype='' ;;
        dosexec )     _mimetype='' _basemimetype='exe' ;;
        shellscript ) _mimetype='' _basemimetype='sh' ;;
        * )
            _basemimetype=$_mimetype
            _mimetype=''
            ;;
    esac
    while read -a _line ;do
        if [ "$_line" == "$_basemimetype" ] ;then
            [ "$_line[1]" ] &&
                _basemimetype=${_line[1]} ||
                _basemimetype=${_basemimetype##*[/.-]}
            break
        fi
        done </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

Bu işlev daha sonra kullanılabilecek bir Bash değişkeni ayarlayabilir:

(Bu, @Petesh doğru cevabından esinlenmiştir):

filename=$(basename "$fullfile")
filename="${filename%.*}"
file2ext "$fullfile" extension

echo "$fullfile -> $filename . $extension"

8

Tamam, eğer doğru anlıyorsam, burada sorun, örneğin birden fazla uzantıya sahip bir dosyanın adını ve tam uzantısını nasıl alacağınızdır stuff.tar.gz.

Bu benim için çalışıyor:

fullfile="stuff.tar.gz"
fileExt=${fullfile#*.}
fileName=${fullfile%*.$fileExt}

Bu size stuffdosya adı ve .tar.gzuzantı olarak verecektir . 0 dahil olmak üzere herhangi bir sayıda uzantı için çalışır. Umarım bu aynı sorunu olan herkes için yardımcı olur =)


Doğru sonuç (göre os.path.splitextOP'nin istediği budur) ('stuff.tar', '.gz').
Cyker

6

Aşağıdaki komut dosyasını kullanıyorum

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

Bu hiç verimli değil. Çok fazla kez çatal yapmak için bu oldukça gereksizdir, çünkü bu işlem herhangi bir harici komuta ve çatallamaya gerek kalmadan saf Bash'te gerçekleştirilebilir.
codeforester

5
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

Bu, bir dosya adında birden çok nokta ve boşluk sağlar, ancak uzantı yoksa dosya adının kendisini döndürür. Gerçi kontrol etmek kolay; sadece dosya adı ve uzantısının aynı olup olmadığını test edin.

Doğal olarak bu yöntem .tar.gz dosyaları için çalışmaz. Ancak bu iki aşamalı bir süreçte ele alınabilir. Uzantı gz ise, katran uzantısı olup olmadığını görmek için tekrar kontrol edin.


5

Balıklarda dosya adı ve uzantısı nasıl çıkarılır :

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

Uyarılar: İçlerinde noktalı dosya adları için iyi çalışan, ancak içinde nokta bulunan uzantılar için iyi olmayan son noktaya böler. Aşağıdaki örneğe bakın.

Kullanımı:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

Bunu yapmanın muhtemelen daha iyi yolları var. Cevabımı geliştirmek için düzenlemekten çekinmeyin.


Ele alacağınız sınırlı sayıda uzantı varsa ve hepsini biliyorsanız, şunu deneyin:

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

Bu , ilk örnek olarak uyarıya sahip değildir , ancak her davayı ele almanız gerekir, böylece kaç uzantıyı beklediğinize bağlı olarak daha sıkıcı olabilir.


4

İşte AWK ile kod . Daha basit yapılabilir. Ama AWK'da iyi değilim.

filename$ ls
abc.a.txt  a.b.c.txt  pp-kk.txt
filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")'
abc.a
a.b.c
pp-kk
filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}'
txt
txt
txt

Son örnekte ilk awk ifadesine ihtiyacınız yok, değil mi?
BHSPitMonkey

Başka bir işlem yaparak Awk ile Awk arasında bağlantı kurabilirsiniz split(). awk -F / '{ n=split($2, a, "."); print a[n] }' uses / `üst düzey sınırlayıcı olarak görünür, ancak daha sonra ikinci alanları ayırır .ve yeni dizideki son öğeyi yazdırır.
üçlü

4

Basitçe kullanın ${parameter%word}

Senin durumunda:

${FILE%.*}

Test etmek istiyorsanız, aşağıdaki tüm çalışmalar ve sadece uzantıyı kaldırın:

FILE=abc.xyz; echo ${FILE%.*};
FILE=123.abc.xyz; echo ${FILE%.*};
FILE=abc; echo ${FILE%.*};

2
Neden inişli çıkışlı? =İşaretlerin etrafında boşluk olmamasına rağmen, yine de faydalıdır .
SilverWolf - Monica'yı

1
Bu iyi çalışıyor. Teşekkür ederim! (şimdi eşit işaretlerin etrafında boşluklar yok, bu yüzden aşağı indirildiyse)
Alex. S.

3

Petesh yanıtından bina , yalnızca dosya adı gerekiyorsa, hem yol hem de uzantı tek bir satırda çıkarılabilir,

filename=$(basename ${fullname%.*})

Benim için çalışmadı: "basename: eksik işlenen Daha fazla bilgi için 'basename --help' komutunu deneyin."
helmy

Garip, Bash kullandığınızdan emin misiniz? Benim durumumda, hem 3.2.25 (eski CentOS) hem de 4.3.30 (Debian Jessie) sürümleriyle kusursuz çalışıyor.
cvr

Belki de dosya adında bir boşluk vardır? Kullanmayı deneyinfilename="$(basename "${fullname%.*}")"
Adrian

İkinci argüman basenameisteğe bağlıdır, ancak kaldırılacak uzantıyı belirtir. Yerine koyma hala faydalı olabilir, ancak belki de basenameaslında değildir, çünkü tüm bu ikameleri kabuk yapılarıyla gerçekleştirebilirsiniz.
tripleee

3

@ Mklement0'ın mükemmel ve rastgele, kullanışlı bashisms'lerin yanı sıra bu / diğer soruların / "o lanet interneti" diğer yanıtların dayandığı ... Hepsini biraz, biraz daha anlaşılır bir şekilde tamamladım, benim (ya da sizin) için ne (benim) daha güçlü bir sürüm / / ne var olması gerektiğini ilgilenen yeniden kullanılabilir işlevi ...bash_profiledirnamebasename

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && return    # demand 2 arguments
    [[ $1 =~ ^(.*/)?(.+)?$ ]] && {     # regex parse the path
        dir=${BASH_REMATCH[1]}
        file=${BASH_REMATCH[2]}
        ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '')
        # edge cases for extensionless files and files like ".nesh_profile.coffee"
        [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))}
        case "$2" in
             dir) echo      "${dir%/*}"; ;;
            name) echo      "${fnr%.*}"; ;;
        fullname) echo "${fnr%.*}.$ext"; ;;
             ext) echo           "$ext"; ;;
        esac
    }
    IFS=$SAVEIFS
}     

Kullanım örnekleri ...

SOMEPATH=/path/to.some/.random\ file.gzip
path $SOMEPATH dir        # /path/to.some
path $SOMEPATH name       # .random file
path $SOMEPATH ext        # gzip
path $SOMEPATH fullname   # .random file.gzip                     
path gobbledygook         # usage: -bash <path> <dir|name|fullname|ext>

1
Güzel yapılmış; birkaç öneri: - Hiç güvenmiyormuş gibi görünmüyorsunuz $IFS(ve eğer olsaydınız local, ayarlamanın etkisini yerelleştirmek için kullanabilirsiniz ). - localDeğişkenleri kullanmak daha iyidir. - Senin hata mesajı çıkışının yapılması gereken stderrdeğil, stdout(kullanım 1>&2) ve bir sıfır olmayan çıkış kodu döndürmesi gerekir. - Daha iyi adlandırmak fullnameiçin basename(eski dir bileşenleri ile bir yolu önerir). - orijinalinde hiç olmasa bile namekoşulsuz olarak bir .(nokta) ekler . basenameYardımcı programı kullanabilirsiniz , ancak bir sonlandırmayı yok saydığını unutmayın /.
mklement0 26:13

2

Basit bir cevap:

POSIX değişkenleri cevabını genişletmek için daha ilginç desenler yapabileceğinizi unutmayın. Burada ayrıntılı olarak açıklanan dava için bunu yapabilirsiniz:

tar -zxvf $1
cd ${1%.tar.*}

Bu, .tar'ın son oluşumunu kesecektir. <bir şey> .

Daha genel olarak, son oluşumunu kaldırmak istiyorsanız. <bir şey> . <something-else> sonra

${1.*.*}

iyi çalışmalı.

Yukarıdaki cevabın bağlantısı ölü gibi görünüyor. İşte TLDP'den doğrudan Bash'de yapabileceğiniz bir dizi dize manipülasyonunun harika bir açıklaması .


Maçı büyük / küçük harfe duyarsız yapmanın bir yolu var mı?
ton

2

Ayrıca boş uzantılara izin vermek istiyorsanız , bu en kısa sürede gelebilirim:

echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION
echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME

1. satır açıkladı: PATH.EXT veya ANYTHING ile eşleşir ve EXT ile değiştirir. HERHANGİ BİRİYLE eşleşirse, ext grubu yakalanmaz.


2

Benim için çalışan tek kişi bu:

path='folder/other_folder/file.js'

base=${path##*/}
echo ${base%.*}

>> file

Bu, dize enterpolasyonunda da kullanılabilir, ancak maalesef baseönceden ayarlamanız gerekir .


1

Burada, isimler kasaya göre çakıştığında adları benzersiz kılmak için bir Bash komut dosyası yazarken bir dosyanın adını ve uzantısını bulmak için kullandığım algoritma.

#! /bin/bash 

#
# Finds 
# -- name and extension pairs
# -- null extension when there isn't an extension.
# -- Finds name of a hidden file without an extension
# 

declare -a fileNames=(
  '.Montreal' 
  '.Rome.txt' 
  'Loundon.txt' 
  'Paris' 
  'San Diego.txt'
  'San Francisco' 
  )

echo "Script ${0} finding name and extension pairs."
echo 

for theFileName in "${fileNames[@]}"
do
     echo "theFileName=${theFileName}"  

     # Get the proposed name by chopping off the extension
     name="${theFileName%.*}"

     # get extension.  Set to null when there isn't an extension
     # Thanks to mklement0 in a comment above.
     extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')

     # a hidden file without extenson?
     if [ "${theFileName}" = "${extension}" ] ; then
         # hidden file without extension.  Fixup.
         name=${theFileName}
         extension=""
     fi

     echo "  name=${name}"
     echo "  extension=${extension}"
done 

Test çalıştırması.

$ config/Name\&Extension.bash 
Script config/Name&Extension.bash finding name and extension pairs.

theFileName=.Montreal
  name=.Montreal
  extension=
theFileName=.Rome.txt
  name=.Rome
  extension=.txt
theFileName=Loundon.txt
  name=Loundon
  extension=.txt
theFileName=Paris
  name=Paris
  extension=
theFileName=San Diego.txt
  name=San Diego
  extension=.txt
theFileName=San Francisco
  name=San Francisco
  extension=
$ 

Bilginize: Harf çevirisi programının tamamını ve daha fazla test örneğini burada bulabilirsiniz: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0


Tüm çözümlerden, dosyanın uzantısı olmadığında boş bir dize döndüren tek çözüm budur:extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '')
f0nzie

1

Örnek dosya kullanarak /Users/Jonathan/Scripts/bash/MyScript.shbu kod:

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

${ME}varlık MyScriptve ${MY_EXT}varlık ile sonuçlanır .sh:


Senaryo:

#!/bin/bash
set -e

MY_EXT=".${0##*.}"
ME=$(/usr/bin/basename "${0}" "${MY_EXT}")

echo "${ME} - ${MY_EXT}"

Bazı testler:

$ ./MyScript.sh 
MyScript - .sh

$ bash MyScript.sh
MyScript - .sh

$ /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

$ bash /Users/Jonathan/Scripts/bash/MyScript.sh
MyScript - .sh

2
Bunun neden bu kadar çok düşüşü olduğundan emin değilim - aslında kabul edilen cevaptan daha verimlidir. (İkinci olarak, aynı zamanda giriş dosya ile keser olmayan bir uzantısı). İçin açık bir yol kullanmak basenamebelki de aşırıdır.
mklement0

1

Yukarıdaki cevaplardan, Python'ları taklit eden en kısa oneliner

file, ext = os.path.splitext(path)

Dosyanızın gerçekten bir uzantısı olduğunu varsayarsak,

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

Bu konuda inişlerim var. Cevabı kaldırmayı düşünüyorum, insanlar bir şekilde hoşlanmıyor.
commonpike

basename uzantıyı değil yalnızca yolu kaldırır.
David Cullen

SUFFIX seçeneğini unuttuğum man sayfasına baktığımdan beri çok uzun zaman geçti.
David Cullen

Ne koyacağınızı bilmeden önce hangi uzantıyı çıkarmak istediğinizi bilmelisiniz, EXTbu yüzden bu kaplumbağalar tamamen aşağı. (Ayrıca, özel değişken adlarınız için tüm büyük harflerden kaçınmalısınız; bunlar sistem değişkenleri için ayrılmıştır.)
üçlü
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.