![Самый короткий способ извлечь последние 3 символа базового (без суффикса) имени файла](https://rvso.com/image/38704/%D0%A1%D0%B0%D0%BC%D1%8B%D0%B9%20%D0%BA%D0%BE%D1%80%D0%BE%D1%82%D0%BA%D0%B8%D0%B9%20%D1%81%D0%BF%D0%BE%D1%81%D0%BE%D0%B1%20%D0%B8%D0%B7%D0%B2%D0%BB%D0%B5%D1%87%D1%8C%20%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BD%D0%B8%D0%B5%203%20%D1%81%D0%B8%D0%BC%D0%B2%D0%BE%D0%BB%D0%B0%20%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE%20(%D0%B1%D0%B5%D0%B7%20%D1%81%D1%83%D1%84%D1%84%D0%B8%D0%BA%D1%81%D0%B0)%20%D0%B8%D0%BC%D0%B5%D0%BD%D0%B8%20%D1%84%D0%B0%D0%B9%D0%BB%D0%B0.png)
Я пытаюсь установить переменную в скрипте 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"
Это ничего не выводит, если такого соответствия нет (если у базового имени нет расширения или если корень перед расширением слишком короткий). В зависимости от ваших требований вы можете настроить регулярное выражение. Это регулярное выражение обеспечивает соблюдение ограничений:
- Он соответствует 3 символам перед конечным расширением (часть после и включая последнюю точку). Эти 3 символа могут содержать точку.
- Расширение может быть пустым (за исключением точки).
- Совпадающая часть и расширение должны быть частью базового имени (часть после последней косой черты).
Использование этого в подстановке команд имеет обычные проблемы с удалением слишком большого количества конечных новых строк, проблема, которая также влияет на ответ Стефана. Это можно решить в обоих случаях, но здесь немного проще:
lastpart=$(
perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x} # allow for possible trailing newline