提取基本檔名最後 3 個字元(減去後綴)的最短方法

提取基本檔名最後 3 個字元(減去後綴)的最短方法

我正在嘗試將 sh 腳本中的變數設定為文件基本名稱的最後 3 個字元(基本名稱的意思是沒有路徑沒有後綴)。我已經成功地做到了這一點,但是,純粹出於好奇,我想知道是否有一個更短的單一命令我可以使用。本來我有一個單行awk,但它相當長。目前我有這個兩行腳本(假設完整的檔案名稱位於$1):

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

例如,“/path/to/somefile.txt”最終以“伊萊”$lastpart

我可以以某種方式組合basename和位元以將後綴剝離為單一命令,並且有沒有辦法在tail不使用管道的情況下將其發送到(或我可以使用的其他東西)?後綴未知,因此我無法將其作為 的參數basename

主要目標其實並不是盡可能短,而是盡可能一目了然。所有這一切的實際背景是這個關於超級用戶的問題,我試著給出一個相當簡單的答案。

答案1

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

###OUTPUT###

456

首先刪除 的最後三個字符,然後從該刪除的結果$var中刪除- 它返回 的最後三個字符。以下是一些更具體的範例,旨在演示如何執行此類操作:$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

$IFS與ting shell 參數結合set也可以是解析和鑽取 shell 變數的非常有效的手段:

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

/這將只為您提供緊接在 中最後一個句點之後的第一個句點之前的三個字元$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如果檔案名稱中可能存在ewline 字符(不適用於本機 shell 解決方案 - 無論如何它們都會處理這個問題):

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

答案2

這是一個典型的工作expr

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

如果您知道您的檔案名稱具有預期的格式(包含一個且僅一個點,並且點之前至少有 3 個字元),則可以簡化為:

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

a000.txt請注意,如果a-00.txt沒有匹配,則退出狀態將是非零,如果匹配部分是解析為 0 的數字。

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 可用,我發現它比其他解決方案更具可讀性,特別是因為它的正則表達式語言更具表現力,並且它具有修飾符/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

相關內容