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 basename
o 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 $var
depois remove $var
os 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 $IFS
com set
parâ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 \n
caractere 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.txt
ou a-00.txt
)
Com zsh
:
file=/path/to/abcdef.txt
lastpart=${${file:t:r}[-3,-1]}
( :t
paracauda(nome base), :r
paradescansar(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 /x
modificador, 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:
- Corresponde aos 3 caracteres antes da extensão final (a parte depois e incluindo o último ponto). Esses 3 caracteres podem conter um ponto.
- A extensão pode estar vazia (exceto o ponto).
- 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