Taban (son ek) son 3 karakter dosya adını almanın en kısa yolu


12

Ben bir dosyanın temel adının son 3 karakter bir sh komut dosyası bir değişken ayarlamak için çalışıyorum (taban adı ile yolu ve sonek olmadan demek ). Bunu yapmayı başardım ama tamamen meraktan, kullanabileceğim daha kısa, tek bir komut olup olmadığını merak ediyorum. Başlangıçta bir astar vardı awk, ama oldukça uzundu. Şu anda (tam bir dosya adı varsayalım) bu iki satır komut dosyası var $1:

filebase=`basename "$1"`
lastpart=`echo -n ${filebase%.*} | tail -c3`

Yani, örneğin, "/path/to/somefile.txt" ile biter kadar "göster ile" içinde $lastpart.

Bir şekilde birleştirebilir miyim basenameve son eki tek bir komut haline tailgetirebilir miyim? Sonek bilinmiyor, bu yüzden bir parametre olarak temel olamaz basename.

Asıl amaç, mümkün olduğunca kısa olmak, mümkün olduğunca kısa bir bakışta okunabilir olmak değildir. Tüm bunların gerçek bağlamı, oldukça basit bir cevap bulmaya çalıştığım Superuser ile ilgili bu soru .


2
Nasıl bir dosyayla başa çıkardınız file.one.two.three? İstediğiniz misiniz ileyoksa two?
terdon

@terdon twoişe yarar ; bunun uzantısı .threesanırım.
Jason C

Yanıtlar:


6

Bu tipik bir iş expr:

$ file=/path/to/abcdef.txt
$ expr "/$file" : '.*\([^/.]\{3\}\)\.[^/.]*$'
def

Dosya adlarınızın beklenen biçime sahip olduğunu biliyorsanız (bir ve yalnızca bir nokta ve noktadan önce en az 3 karakter içerir), bu basitleştirilebilir:

expr "/$file" : '.*\(.\{3\}\)\.'

Eşleşme yoksa çıkış durumunun sıfır olmayacağını, ancak eşleşen parça 0 olarak çözülen bir sayı ise ( a000.txtveya için a-00.txt) unutmayın.

İle zsh:

file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}

( :tİçin kuyruk (İşlevi) :riçin geri kalan (uzantısı çıkarılmış)).


2
Güzel. expraşina olmam gereken başka bir şey. Ben gerçekten böyle zshgenel çözümleri (Sadece sol tarafında iç içe değiştirmeler için verdiği destek okuyordum ${}çok dün ve kalmak istemeyerek shaynı vardı), her defasında varsayılan olarak mevcut olmadığı sadece serseri var.
Jason C

2
@JasonC - bilgi en önemlisidir. Sistemin tüm noktası bu şekilde olabildiğince erişilebilir olsun. Temsilci gıda aldım ben üzgün olabilir, ama daha sık (asla) bilgi eve pastırma getiriyor
mikeserv

1
@mikeserv "İstek: Pastırma için takas temsilcisi"; buraya meta bakıyorum ben geliyorum.
Jason C

1
@mikerserv, sizinki POSIX, yalnızca yerleşikleri kullanıyor ve herhangi bir işlemi çatallamıyor. Komut yerine koyma kullanmamanız, sondaki yeni satırlarla ilgili sorunlardan kaçınmanız anlamına da gelir, bu da iyi bir yanıttır.
Stéphane Chazelas

1
@mikeserv, ben ima etmek istemedim exproldu değil POSIX. Bu kesinlikle. Ancak nadiren yerleşik.
Stéphane Chazelas

13
var=123456
echo "${var#"${var%???}"}"

###OUTPUT###

456

Bu, ilk önce son üç karakteri $varkaldırır , daha sonra $varbu kaldırma işleminin sonuçlarından kaldırılır. Bu, son üç karakterini döndürür $var. Daha spesifik olarak böyle bir şeyi nasıl yapabileceğinizi göstermeyi amaçlayan bazı örnekler:

touch file.txt
path=${PWD}/file.txt
echo "$path"

/tmp/file.txt

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

file
txt
file.txt
/tmp/file.txt

Bunu çok fazla komutla yaymanız gerekmiyor. Bunu sıkıştırabilirsiniz:

{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

Kabuk parametrelerini birleştirmek $IFS, setkabuk değişkenleri arasında ayrıştırma ve delme için de çok etkili bir yöntem olabilir:

(IFS=. ; set -f; set -- ${path##*/}; printf %s "${1#"${1%???}"}")

Geriye sadece üç karakter hemen son izleyen ilk döneminden önceki alacak /in $path. Eğer yalnızca ilk üç karakteri hemen son önceki almak istiyorsanız .içinde $path (örneğin, birden fazla bir ihtimal varsa .dosya olarak) :

(IFS=.; set -f; set -- ${path##*/}; ${3+shift $(($#-2))}; printf %s "${1#"${1%???}"}")

Her iki durumda da şunları yapabilirsiniz:

newvar=$(IFS...)

Ve...

(IFS...;printf %s "$2")

... aşağıdakileri takip eder .

Harici bir program kullanmayı düşünmezseniz şunları yapabilirsiniz:

printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

\nDosya adında ewline karakter olasılığı varsa (yerel kabuk çözümleri için geçerli değildir - hepsi yine de bunu yapar) :

printf %s "${path##*/}" | sed 'H;$!d;g;s/.*\(...\)\..*/\1/'

1
Öyle, teşekkürler. Ayrıca dokümantasyon da buldum . Ama son 3 karakteri $baseoradan almak için yapabileceğim en iyi şey üç satırlıktı name=${var##*/} ; base=${name%%.*} ; lastpart=${base#${base%???}}. Artı tarafta saf bash, ama hala 3 satır. ("/Tmp/file.txt" örneğinizde "dosya" yerine "ile" gerekir.) Parametre değiştirme hakkında çok şey öğrendim; Bunu yapabileceğine dair hiçbir fikrim yoktu ... oldukça kullanışlı. Kişisel olarak da çok okunabilir buluyorum.
Jason C

1
@JasonC - bu tamamen taşınabilir bir davranıştır - bash'a özgü değildir. Ben okumanızı tavsiye bu .
mikeserv

1
Eh, sanırım, soneki kaldırmak %yerine kullanabilirim %%ve aslında yolu soymam gerekmiyor, bu yüzden daha güzel, iki satır alabilirim noextn=${var%.*} ; lastpart=${noextn#${noextn%???}}.
Jason C

1
@ JasonC - evet, işe yarayacak gibi görünüyor. Varsa bu kıracak $IFSiçinde ${noextn}ve genişleme alıntı yapma. Yani, bu daha güvenli:lastpart=${noextn#"${noextn%???}"}
mikeserv

1
@JasonC - Eğer yararlı yukarıda bulursa son, bakmak isteyebilirsiniz bu . Diğer parametre genişletme biçimleriyle ilgilidir ve bu soruya verilen diğer cevaplar da gerçekten iyidir. Ve içinde aynı konuda iki cevap daha var. Eğer istersen.
mikeserv

4

Kullanabiliyorsanız perl:

lastpart=$(
    perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)

Bu süper. ny oy aldım.
mikeserv

Daha özlü bit: perl -e 'shift =~ /(.{3})\.[^.]*$/ && print $1' $filename. basenameDosya adı sonek içeriyorsa ancak yoldaki bazı dizinler varsa ek bir gereksinim olacaktır.
Dubu

@Dubu: Dosya adının soneki yoksa çözümünüz her zaman başarısız olur.
cuonglm

1
@Gnouc Bu niyetle yapıldı. Ama haklısın, amaca bağlı olarak bu yanlış olabilir. Alternatif:perl -e 'shift =~ m#(.{3})(?:\.[^./]*)?$# && print $1' $filename
Dubu

2

sed bunun için çalışır:

[user@host ~]$ echo one.two.txt | sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|'
two

Veya

[user@host ~]$ sed -r 's|(.*)\..*$|\1|;s|.*(...)$|\1|' <<<one.two.txt
two

Eğer senin seddesteklemediği -r, sadece örneklerini değiştirmek ()ile \(ve \)ve sonra -rgerekli değildir.


1

Perl mevcutsa, diğer çözümlerden daha okunabilir olabileceğini düşünüyorum, özellikle regex dili daha etkileyici olduğundan ve /xdaha net regex'ler yazmanıza izin veren değiştiriciye sahip olduğundan:

perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

Böyle bir eşleşme yoksa, hiçbir şey yazdırılmaz (taban adında uzantı yoksa veya uzantıdan önceki kök çok kısasa). Gereksinimlerinize bağlı olarak normal ifadeyi ayarlayabilirsiniz. Bu normal ifade kısıtlamaları uygular:

  1. Son uzantıdan önceki 3 karakterle (son noktadan sonraki ve son nokta dahil olmak üzere) eşleşir. Bu 3 karakter bir nokta içerebilir.
  2. Uzantı boş olabilir (nokta hariç).
  3. Eşleşen parça ve uzantı, taban adının bir parçası olmalıdır (son eğik çizgiden sonraki parça).

Bunu bir komut yerine koymada kullanmak, çok fazla takip eden satırsonu kaldırmakla ilgili normal sorunlara sahiptir, bu da Stéphane'nin cevabını da etkiler. Her iki durumda da ele alınabilir, ancak burada biraz daha kolaydır:

lastpart=$(
  perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline

0

Python2.7

$ echo /path/to/somefile.txt | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
ile

$ echo file.one.two.three | python -c "import sys, os; print '.'.join(os.path.basename(sys.stdin.read()).split('.')[:-1])[-3:]"
two

0

Bence bu bash işlevi, pathStr (), aradığınızı yapacak.

Awk, sed, grep, perl veya expr gerektirmez. Sadece bash yerleşiklerini kullanır, bu yüzden oldukça hızlıdır.

Ayrıca bağımlı argsNumber ve isOption işlevlerini dahil ettik, ancak işlevleri kolayca pathStr içine dahil edilebilir.

Bağımlı fonksiyon ifHelpShow, YAD yoluyla terminal metninde veya GUI iletişim kutusuna yardım metninin çıktısı için çok sayıda alt bağımlılığa sahip olduğundan dahil edilmez . Bu belgeye iletilen yardım metni dokümantasyon için dahil edilmiştir. İfHelpShow ve bağımlılarını isteyip istemediğinizi bildirin.

function  pathStr () {
  ifHelpShow "$1" 'pathStr --OPTION FILENAME
    Given FILENAME, pathStr echos the segment chosen by --OPTION of the
    "absolute-logical" pathname. Only one segment can be retrieved at a time and
    only the FILENAME string is parsed. The filesystem is never accessed, except
    to get the current directory in order to build an absolute path from a relative
    path. Thus, this function may be used on a FILENAME that does not yet exist.
    Path characteristics:
        File paths are "absolute" or "relative", and "logical" or "physical".
        If current directory is "/root", then for "bashtool" in the "sbin" subdirectory ...
            Absolute path:  /root/sbin/bashtool
            Relative path:  sbin/bashtool
        If "/root/sbin" is a symlink to "/initrd/mnt/dev_save/share/sbin", then ...
            Logical  path:  /root/sbin/bashtool
            Physical path:  /initrd/mnt/dev_save/share/sbin/bashtool
                (aka: the "canonical" path)
    Options:
        --path  Absolute-logical path including filename with extension(s)
                  ~/sbin/file.name.ext:     /root/sbin/file.name.ext
        --dir   Absolute-logical path of directory containing FILENAME (which can be a directory).
                  ~/sbin/file.name.ext:     /root/sbin
        --file  Filename only, including extension(s).
                  ~/sbin/file.name.ext:     file.name.ext
        --base  Filename only, up to last dot(.).
                  ~/sbin/file.name.ext:     file.name
        --ext   Filename after last dot(.).
                  ~/sbin/file.name.ext:     ext
    Todo:
        Optimize by using a regex to match --options so getting argument only done once.
    Revised:
        20131231  docsalvage'  && return
  #
  local _option="$1"
  local _optarg="$2"
  local _cwd="$(pwd)"
  local _fullpath=
  local _tmp1=
  local _tmp2=
  #
  # validate there are 2 args and first is an --option
  [[ $(argsNumber "$@") != 2 ]]                        && return 1
  ! isOption "$@"                                      && return 1
  #
  # determine full path of _optarg given
  if [[ ${_optarg:0:1} == "/" ]]
  then
    _fullpath="$_optarg"
  else
    _fullpath="$_cwd/$_optarg"
  fi
  #
  case "$_option" in
   --path)  echo "$_fullpath"                            ; return 0;;
    --dir)  echo "${_fullpath%/*}"                       ; return 0;;
   --file)  echo "${_fullpath##*/}"                      ; return 0;;
   --base)  _tmp1="${_fullpath##*/}"; echo "${_tmp1%.*}" ; return 0;;
    --ext)  _tmp1="${_fullpath##*/}";
            _tmp2="${_tmp1##*.}";
            [[ "$_tmp2" != "$_tmp1" ]]  && { echo "$_tmp2"; }
            return 0;;
  esac
  return 1
}

function argsNumber () {
  ifHelpShow "$1" 'argsNumber "$@"
  Echos number of arguments.
  Wrapper for "$#" or "${#@}" which are equivalent.
  Verified by testing on bash 4.1.0(1):
      20140627 docsalvage
  Replaces:
      argsCount
  Revised:
      20140627 docsalvage'  && return
  #
  echo "$#"
  return 0
}

function isOption () {
  # isOption "$@"
  # Return true (0) if argument has 1 or more leading hyphens.
  # Example:
  #     isOption "$@"  && ...
  # Note:
  #   Cannot use ifHelpShow() here since cannot distinguish 'isOption --help'
  #   from 'isOption "$@"' where first argument in "$@" is '--help'
  # Revised:
  #     20140117 docsalvage
  # 
  # support both short and long options
  [[ "${1:0:1}" == "-" ]]  && return 0
  return 1
}

KAYNAKLAR


Anlamadım - zaten burada benzer şekilde tamamen portatif olarak - izms olmadan bash- görünüşte bundan daha basit bir şekilde nasıl gösterildi . Ayrıca, nedir ${#@}?
mikeserv

Bu sadece işlevselliği yeniden kullanılabilir bir işleve paketler. re: $ {# @} ... Dizileri ve elemanlarını değiştirmek için tam değişken gösterimi $ {} gerekir. $ @, argümanların 'dizisidir'. $ {# @}, argüman sayısının bash sözdizimidir.
DocSalvager

Hayır, $#argüman sayısının sözdizimidir ve burada başka yerlerde de kullanılır.
mikeserv

"$ #" İfadesinin "bağımsız değişken sayısı" için yaygın olarak belgelenmiş bir stataks olması haklısınız. Ancak, "$ {# @}" ifadesinin eşdeğer olduğunu doğruladım. Konumsal argümanlar ve diziler arasındaki farklılıkları ve benzerlikleri denedikten sonra bununla yaralandım. Daha sonra, daha kısa, daha basit "$ #" sözdiziminin eş anlamlısı olan dizi sözdiziminden geliyor. Ben değiştirdim ve "$ #" kullanmak için argsNumber () belgeledi. Teşekkürler!
DocSalvager

${#@}çoğu durumda eşdeğer değildir - POSIX Spec üzerinde herhangi bir parametre açılımları sonuçlarını bildiren ya $@ya $*maalesef belirtilmemiş bulunmaktadır. Çalışabilir bashama bu güvenilir bir özellik değil, sanırım söylemeye çalıştığım şey bu.,
mikeserv
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.