![提取基本檔名最後 3 個字元(減去後綴)的最短方法](https://rvso.com/image/38704/%E6%8F%90%E5%8F%96%E5%9F%BA%E6%9C%AC%E6%AA%94%E5%90%8D%E6%9C%80%E5%BE%8C%203%20%E5%80%8B%E5%AD%97%E5%85%83%EF%BC%88%E6%B8%9B%E5%8E%BB%E5%BE%8C%E7%B6%B4%EF%BC%89%E7%9A%84%E6%9C%80%E7%9F%AD%E6%96%B9%E6%B3%95.png)
我正在嘗試將 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"
如果沒有這樣的符合(如果基本名稱沒有副檔名或副檔名之前的根太短),則不會列印任何內容。根據您的要求,您可以調整正規表示式。此正規表示式強制執行約束:
- 它匹配最終擴展名之前的 3 個字元(最後一個點之後並包括最後一個點的部分)。這 3 個字元可以包含一個點。
- 擴展名可以為空(點除外)。
- 匹配的部分和擴展名必須是基本名稱的一部分(最後一個斜杠後面的部分)。
在命令替換中使用它會出現刪除太多尾隨換行符的常見問題,這個問題也會影響 Stéphane 的答案。這兩種情況都可以處理,但這裡比較容易:
lastpart=$(
perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x} # allow for possible trailing newline