Maneira mais curta de extrair os últimos 3 caracteres do nome do arquivo base (menos o sufixo)

Maneira mais curta de extrair os últimos 3 caracteres do nome do arquivo base (menos o sufixo)

Estou tentando definir uma variável em um script sh para os últimos 3 caracteres do nome base de um arquivo (por nome base quero dizer sem o caminhoesem o sufixo). Consegui fazer isso, mas, por pura curiosidade, estou me perguntando se existe um comando único e mais curto que eu possa usar. Originalmente eu tinha uma frase com awk, mas era bastante longa. Atualmente tenho este script de duas linhas (assumindo que um nome de arquivo completo esteja $1):

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

Então, por exemplo,"/caminho/para/algumarquivo.txt"acaba com"ile"em $lastpart.

Posso de alguma forma combinar basenameo bit para separar o sufixo em um único comando, e existe uma maneira de enviá-lo tail(ou algo mais que eu possa usar) sem usar um canal? O sufixo é desconhecido, então não posso baseá-lo como parâmetro para basename.

O objetivo principal não é ser o mais curto possível, mas sim o mais legível possível à primeira vista. O contexto real de tudo isso éesta pergunta no superusuário, onde estou tentando encontrar uma resposta razoavelmente simples.

Responder1

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

###OUTPUT###

456

Isso primeiro remove os três últimos caracteres e $vardepois remove $varos resultados dessa remoção - que retorna os três últimos caracteres de $var. Aqui estão alguns exemplos mais especificamente destinados a demonstrar como você pode fazer tal coisa:

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

Você não precisa espalhar tudo isso por meio de tantos comandos. Você pode compactar isso:

{
    base=${path##*/} exten= 
    printf %s\\n "${base%.*}" "${exten:=${base#"${base%???}"}}" "$base" "$path"
    echo "$exten"
}

file 
txt 
file.txt 
/tmp/file.txt
txt

A combinação $IFScom setparâmetros do shell ting também pode ser um meio muito eficaz de analisar e detalhar variáveis ​​do shell:

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

Isso fornecerá apenas os três caracteres imediatamente anteriores ao primeiro período após o /último $path. Se você quiser recuperar apenas os três primeiros caracteres imediatamente anteriores ao .último$path (por exemplo, se houver a possibilidade de mais de um .nome de arquivo):

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

Em ambos os casos você pode fazer:

newvar=$(IFS...)

E...

(IFS...;printf %s "$2")

... imprimirá o que segue o.

Se você não se importa em usar um programa externo, você pode fazer:

printf %s "${path##*/}" | sed 's/.*\(...\)\..*/\1/'

Se houver uma chance de um \ncaractere ewline no nome do arquivo(não aplicável para soluções de shell nativas - todas elas lidam com isso de qualquer maneira):

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

Responder2

Esse é um trabalho típico para expr:

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

Se você sabe que os nomes dos seus arquivos têm o formato esperado (contém um e apenas um ponto e pelo menos 3 caracteres antes do ponto), isso pode ser simplificado para:

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

Observe que o status de saída será diferente de zero se não houver correspondência, mas também se a parte correspondente for um número que resolve 0. (como for a000.txtou a-00.txt)

Com zsh:

file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}

( :tparacauda(nome base), :rparadescansar(com extensão removida)).

Responder3

Se você puder usar perl:

lastpart=$(
    perl -e 'print substr((split(/\.[^.]*$/,shift))[0], -3, 3)
            ' -- "$(basename -- "$1")"
)

Responder4

Se perl estiver disponível, acho que pode ser mais legível do que outras soluções, especificamente porque sua linguagem regex é mais expressiva e possui o /xmodificador, que permite escrever regexs mais claras:

perl -e 'print $1 if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"

Isto não imprime nada se não houver tal correspondência (se o nome base não tiver extensão ou se a raiz antes da extensão for muito curta). Dependendo dos seus requisitos, você pode ajustar o regex. Este regex impõe as restrições:

  1. Corresponde aos 3 caracteres antes da extensão final (a parte depois e incluindo o último ponto). Esses 3 caracteres podem conter um ponto.
  2. A extensão pode estar vazia (exceto o ponto).
  3. A parte correspondente e a extensão devem fazer parte do nome base (a parte após a última barra).

Usar isso em uma substituição de comando tem problemas normais com a remoção de muitas novas linhas finais, um problema que também afeta a resposta de Stéphane. Isso pode ser resolvido em ambos os casos, mas é um pouco mais fácil aqui:

lastpart=$(
  perl -e 'print "$1x" if shift =~ m{ ( [^/]{3} ) [.] [^./]* \z }x' -- "$file"
)
lastpart=${lastpart%x}  # allow for possible trailing newline

informação relacionada