Самый короткий способ извлечь последние 3 символа базового (без суффикса) имени файла

Самый короткий способ извлечь последние 3 символа базового (без суффикса) имени файла

Я пытаюсь установить переменную в скрипте sh на последние 3 символа базового имени файла (под базовым именем я подразумеваю без путиибез суффикса). Мне это удалось, но чисто из любопытства мне интересно, есть ли более короткая, одна команда, которую я могу использовать. Изначально у меня была однострочная команда с awk, но она была довольно длинной. Сейчас у меня есть этот двухстрочный скрипт (предполагается, что полное имя файла находится в $1):

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

Так, например,"/путь/к/какому-то/файлу.txt"заканчивается тем, что"иль"в $lastpart.

Могу ли я как-то объединить basenameи бит, чтобы отделить суффикс в одну команду, и есть ли способ отправить его tail(или что-то еще, что я могу использовать) без использования канала? Суффикс неизвестен, поэтому я не могу использовать его в качестве параметра для basename.

Основная цель на самом деле не столько в том, чтобы быть максимально коротким, сколько в том, чтобы быть максимально читаемым с первого взгляда. Фактический контекст всего этого таковэтот вопрос на Superuser, где я пытаюсь найти достаточно простой ответ.

решение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с setпараметрами оболочки ting также может быть очень эффективным средством анализа и детализации переменных оболочки:

(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 в имени файла(неприменимо для собственных решений оболочки — они все с этим справляются):

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

решение2

Это типичная работа для expr:

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

Если вы знаете, что имена ваших файлов имеют ожидаемый формат (содержат одну и только одну точку и не менее 3 символов перед точкой), это можно упростить до:

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

Обратите внимание, что статус выхода будет ненулевым, если совпадений нет, а также если совпавшая часть представляет собой число, которое разрешается в 0. (как для a000.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, я считаю, что он может быть более читабельным, чем другие решения, в частности потому, что его язык регулярных выражений более выразителен и имеет модификатор /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

Связанный контент