ベース(末尾のマイナス)ファイル名の最後の3文字を抽出する最短の方法


12

私は、ファイルのベース名(ベース名によって、私はパスなしで意味の最後の3つの文字にshスクリプト内の変数を設定しようとしている接尾辞なし)。私はこれを行うことに成功しましたが、純粋に好奇心から、より短い単一のコマンドを使用できるかどうか疑問に思っています。もともと、私はでワンライナーを持っていましたawkが、それはかなり長かったです。現在、私はこの2行のスクリプトを持っています(完全なファイル名がにあると仮定$1):

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

したがって、たとえば、「/ path / to / somefile.txt」は、「ile」で終わり$lastpartます。

どういうわけかbasename、ビットを組み合わせて接尾辞を単一のコマンドに取り除くtailことができますか?接尾辞は不明なので、のパラメーターとして使用することはできませんbasename

主な目標は、実際にはできるだけ短くすることではなく、できるだけ一目で読めるようにすることです。このすべての実際のコンテキストは、スーパーユーザーに関するこの質問です。ここでは、かなり単純な答えを考えています。


2
どのようなファイルをどのように扱いますfile.one.two.threeか?欲しいですiletwo
テルドン

@terdon twoは動作します。その拡張は.three私が推測するでしょう。
ジェイソンC 14年

回答:


6

それは典型的な仕事ですexpr

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

ファイル名が予想される形式(1つだけのドットと、ドットの前に少なくとも3文字を含む)であることがわかっている場合は、次のように簡略化できます。

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

一致がない場合は終了ステータスがゼロ以外になりますが、一致した部分が0に解決される数値である場合にも注意してください(for a000.txta-00.txt

zsh

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

:tのための(ベース名)、:rのための休息(拡張子は削除して))。


2
いいね exprもう1つ知っておく必要があります。私は本当にzsh一般的なソリューション好きです(${}昨日の左側のネストされた置換のサポートについて読んでshいて、同じことを望んでいました)、それはデフォルトでは常に存在するとは限らないのは残念です。
ジェイソンC

2
@JasonC-情報が最も重要です。できる限りアクセスしやすくしてください-とにかくシステムの要点です。担当者が食品を購入した場合、私は怒るかもしれないが、より頻繁に(決してより)情報ホームもたらしベーコン
mikeserv

1
@mikeserv「リクエスト:ベーコンと交換」ここでメタを見てください
ジェイソンC 14年

1
@mikerservは、POSIXであり、組み込みのみを使用し、プロセスをフォークしません。また、コマンド置換を使用しないことは、後続の改行に関する問題を回避することを意味するため、これも良い答えです。
ステファンシャゼル14年

1
@mikeserv、私はPOSIXではないことを意味するつもりexprはありませんでした。確かにそうです。ただし、ほとんど組み込まれていません。
ステファンシャゼル14年

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

###OUTPUT###

456

最初に最後の3文字を$var削除してから$var、その削除の結果から削除します$var。これにより、最後の3文字が返されます。そのようなことをどのように行うかを示すことをより具体的に目的としたいくつかの例を次に示します。

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

これを非常に多くのコマンドで広げる必要はありません。これを圧縮できます:

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

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

シェルパラメータの組み合わせ$IFSと組み合わせるsetことも、シェル変数を解析およびドリルするための非常に効果的な方法です。

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

それはあなたが唯一の3つの文字はすぐに最後に続く最初の期間、先行取得します/では$path。あなたが最初の三文字だけを取得したい場合は、すぐに最後の直前.での$path (複数の可能性がある場合には、例えば.ファイル名には)

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

どちらの場合でも次のことができます。

newvar=$(IFS...)

そして...

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

...後に続くものを印刷します .

外部プログラムを使用してもかまわない場合は、以下を実行できます。

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

\nファイル名に改行文字が含まれる可能性がある場合(ネイティブシェルソリューションには適用されません-とにかくすべて処理されます)

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

1
ありがと。ドキュメントも見つけました。しかし、$baseそこから最後の3文字を取得するには、3行name=${var##*/} ; base=${name%%.*} ; lastpart=${base#${base%???}}でした。プラス面は純粋なbashですが、まだ3行です。(「/tmp/file.txt」の例では、「file」ではなく「ile」が必要です。)パラメーターの置換について多くのことを学びました。それができるとは思いもしませんでした...かなり便利です。個人的にも非常に読みやすいと思います。
ジェイソンC 14年

1
@JasonC-これは完全に移植可能な動作です-bash固有ではありません。これを読むことをお勧めます。
mikeserv 14年

1
さて、サフィックスを削除する%代わりに使用でき%%、実際にパスを削除する必要はないので、より良い2行を取得できますnoextn=${var%.*} ; lastpart=${noextn#${noextn%???}}
ジェイソンC 14年

1
@JasonC-はい、それは動作するように見えます。ある場合には壊れます$IFS${noextn}、あなたが拡張を引用しないでください。だから、これは安全です:lastpart=${noextn#"${noextn%???}"}
mikeserv

1
@JasonC-最後に、上記が有用であることがわかった場合、これを見てください。他の形式のパラメーター展開を扱っており、その質問に対する他の回答も非常に優れています。そして、同じ主題に関する他の2つの回答へのリンクがあります。お望みならば。
mikeserv 14年

4

使用できる場合perl

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

それはクールだ。ny票を得た。
mikeserv 14年

Aは、より簡潔なビット:perl -e 'shift =~ /(.{3})\.[^.]*$/ && print $1' $filenamebasenameファイル名にサフィックスが含まれていないが、パス内の一部のディレクトリに含まれている場合、追加が必要です。
ドゥブ14年

@Dubu:ファイル名にサフィックスがない場合、ソリューションは常に失敗します。
cuonglm 14年

1
@Gnoucこれは意図によるものです。しかし、あなたは正しい、これは目的に応じて間違っている可能性があります。代替手段:perl -e 'shift =~ m#(.{3})(?:\.[^./]*)?$# && print $1' $filename
ドゥブ14年

2

sed これのために働く:

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

または

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

sedがをサポートしていない-r場合は()\(とのインスタンスを置き換えるだけ\)で、その後-rは必要ありません。


1

perlが利用可能な場合、他のソリューションよりも読みやすいことがわかります。具体的には、正規表現言語はより表現力があり、/x修飾子があり、より明確な正規表現を作成できるためです。

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

このような一致がない場合(ベース名に拡張子がない場合、または拡張子の前のルートが短すぎる場合)は、何も出力しません。要件に応じて、正規表現を調整できます。この正規表現は制約を強制します:

  1. 最後の拡張子の前の3文字(最後のドット以降の部分)に一致します。これらの3文字にはドットを含めることができます。
  2. 拡張子は空にすることができます(ドットを除く)。
  3. 一致した部分と拡張子は、ベース名の一部(最後のスラッシュの後の部分)でなければなりません。

これをコマンド置換で使用すると、後続の改行が多すぎるという通常の問題が発生します。この問題は、ステファンの回答にも影響します。どちらの場合でも対処できますが、ここでは少し簡単です。

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

このbash関数pathStr()は、あなたが探しているものを実行すると思います。

awk、sed、grep、perl、exprは必要ありません。bashビルトインのみを使用するため、非常に高速です。

依存するargsNumberおよびisOption関数も含めましたが、それらの機能はpathStrに簡単に組み込むことができます。

端末のコマンドラインまたはYADを介してGUIダイアログボックスにヘルプテキストを出力するための多数の下位依存関係があるため、依存関数ifHelpShowは含まれていません。渡されたヘルプテキストは、ドキュメントに含まれています。ifHelpShowとその依存関係が必要な場合にアドバイスします。

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
}

リソース


私は理解していません-すでにここでデモされていますが、これは同様に完全に移植可能です- bashイズムなしで-これよりも一見シンプルです。また、何${#@}ですか?
mikeserv 14年

これは、機能を再利用可能な関数にパッケージ化するだけです。re:$ {#@} ...配列とその要素を操作するには、完全な変数表記$ {}が必要です。$ @は引数の「配列」です。$ {#@}は引数の数のbash構文です。
DocSalvager 14年

いいえ、$#引数の数の構文であり、ここで使用されています。
mikeserv 14年

「引数の数」の「$#」が広く文書化されたシスタックスであることは正しいです。ただし、「$ {#@}」が同等であることを再確認しました。位置引数と配列の違いと類似点を実験した後、私はそれを見つけました。後者は、より短く、より単純な "$#"構文の同義語である配列構文に由来します。「$#」を使用するようにargsNumber()を変更および文書化しました。ありがとう!
DocSalvager 14年

${#@}ほとんどの場合、同等ではありません- 残念ながら、POSIX仕様では、いずれかのパラメータ拡張の結果が指定されているか$@$*指定されていません。動作するかもしれbashませんが、それは信頼できる機能ではありません。私が言いたいことだと思います。
mikeserv 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.