基本ファイル名の最後の3文字を抽出する最も短い方法(サフィックス減算)

基本ファイル名の最後の3文字を抽出する最も短い方法(サフィックス減算)

shスクリプトの変数をファイルのデフォルト名の最後の3文字に設定しようとしています(デフォルト名はパスがないことを意味します)。そしてサフィックスなし)。これはうまくいきましたが、純粋な好奇心として使用できる短い単一のコマンドがあるかどうかを知りたいです。もともとは一行でしたawkが、かなり長かったです。現在、次の2行のスクリプトがあります(フルファイル名があると仮定$1)。

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

例えば、"/パス/to/somefile.txt"最後に「エリー」存在する$lastpart

何とかビットを組み合わせて、basenameサフィックスを単一のコマンドで削除できますか?tailパイプを使わずにサフィックス(または利用可能な他の項目)に送信する方法はありますか?サフィックスがわからないため、パラメータとして渡すことはできませんbasename

実際、主な目標はできるだけ短く書くのではなく、できるだけ明確に書くことです。これらすべての実際の背景は次のとおりです。スーパーユーザーに関する質問です。、私は非常に簡単な答えをしようとしました。

答え1

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

###OUTPUT###

456

まず、最後の3文字を削除し、次に削除$var結果から最後の3文字を削除します。以下は、このようなタスクを実行する方法を示すために設計されたいくつかの具体的な例です。$var$var

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

$IFStingシェルパラメータと組み合わせると、setシェル変数を解析して掘削するための非常に効果的な手段になる可能性があります。

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

/これにより、最後のピリオドの後に最初のピリオドの直前の3文字のみが表示されます$path.最後の文字より前の最初の3文字だけを検索したい場合$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/'

答え2

一般的な作業は次のとおりですexpr

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

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

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

一致するものがない場合は、終了ステータスは0ではなく、一致部分が0と見なされる数値であることに注意してください。 (例:fora000.txtまたはa-00.txt

そしてzsh

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

(:tのため(基本名:r残り(拡張子の除去)).

答え3

利用可能な場合perl

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

答え4

Perlが利用可能な場合は、他の解決策よりも読みやすいと思います。これは、Perlの正規表現言語がより表現力に優れ、より/x明確な正規表現を作成できる修飾子があるためです。

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

一致するものがない場合(デフォルト名に拡張子がない場合、または拡張子の前のルートが短すぎる場合)、何も印刷されません。要件に応じて正規表現を調整できます。この正規表現は制約を適用します。

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

コマンドの置き換えにこれを使用すると、末尾の改行文字が削除されすぎるという一般的な問題が発生し、これはStéphaneの答えにも影響します。どちらの場合も処理できますが、ここは簡単です。

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

関連情報