ベースファイル名(サフィックスを除く)の最後の 3 文字を抽出する最も短い方法

ベースファイル名(サフィックスを除く)の最後の 3 文字を抽出する最も短い方法

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

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

例えば、"/path/to/somefile.txt"結局「イル」$lastpart

basenameとビットを何らかの方法で組み合わせて、サフィックスを 1 つのコマンドに削除することはできますか。またtail、パイプを使用せずに に (または使用できる他の何か) を送信する方法はありますか。サフィックスが不明であるため、 へのパラメーターとしてベースにすることはできませんbasename

実際のところ、主な目標はできるだけ短くすることではなく、できるだけ一目で読みやすいようにすることです。これらすべての実際の文脈はこの質問はSuperuserで、ここではかなり単純な答えを出そうとしています。

答え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 以外になることに注意してください。( または の場合のようa000.txta-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

関連情報