Bash 4.3 de uma linha
Digamos que não precisamos de um roteiro completo. Bash tem recursos suficientes que nos permitem fugir com um one-liner. Aqui está um:
bash-4.3$ (read -r var ;IFS='/'; printf "%c" ${var%/*};echo ${var##*[^0-9]}) <<< "sv/de/e11"
sd11
O que está acontecendo?
- tudo acontece em subshell, portanto
( )
em todo o comando
- usamos aqui a string
<<<
para enviar entrada, e o comando subshell obtém via read -r var
e armazena em var
variable
- definimos
IFS='/'
para que o subshell divida var
em itens separados no separador /
. Isso é importante para a divisão de palavras.
- em seguida, usamos a remoção do sufixo
${var%/*}
para nos livrarmos da última parte antes de /
. No exemplo acima, seria e11
-
printf "%c"
verá o resultado de ${var%/*}
como sv de
devido à remoção de palavras e sufixos mencionados acima (mágica, sim). Por causa de como printf
words, %c
imprimirá apenas o primeiro caractere, mas fará isso para cada argumento de linha de comando que receber, assim, para sv de
, será gerada s
e d
. A impressão é feita sem nova linha, portanto, parece que os caracteres são digitados em seqüência
-
echo ${var##*[^0-9]}
faz uso da remoção de prefixo para se livrar de todos os caracteres não dígitos na string de entrada fornecida, obtendo assim apenas os últimos dígitos
Existe outra abordagem de uma linha, que é um pouco mais explícita e natural para programadores tipo C.
bash-4.3$ (read -r inp;IFS='/';arr=( $inp ); for ((i=0;i<$(( ${#arr[@]} -1 ));i++));do printf "%s" ${arr[$i]:0:1};done;printf "%s\n" ${inp##*[^0-9]}) <<< "sv/de/e11"
sd11
O que é essa mágica? Aqui está uma explicação:
- Tudo acontece em subshell, portanto,
()
em todo o comando.
- Usamos aqui a string
<<<
para enviar o item desejado para o fluxo de stdin do comando, e o comando obtém o comando read -r inp
e o armazena em inp
variable
- Em seguida, alteramos a variável
IFS
para dividir tudo em uma matriz.
- iteramos todos os itens até o anterior ao último usando o estilo C para o loop
for ((initial condition; test condition; post condition)) ; do ... done
- o
$(( ${#arr[@]} - 1 ))
é a expansão aritmética, onde subtraímos 1 do comprimento da matriz ${#arr[@]}
- o
printf "%s" ${arr[$i]:0:1}
nos permite usar a expansão de parâmetro, onde imprimimos apenas o primeiro caractere de cada item, e printf "%s"
imprime sem nova linha, assim parece que estamos imprimindo cada letra na mesma linha.
- finalmente, depois que o loop terminar, pegamos o texto de entrada original e nos livramos de tudo que não é dígito usando a remoção de prefixo
${#*[^0-9]}
Abordagem de script
Como a pergunta pede por um shell script, aqui está um em bash
4.3, que é quase a mesma abordagem acima, mas mais explícito:
#!/bin/bash
IFS='/'
items=( )
counter=1
for i in ${items[@]}
do
if [ $counter -eq ${#items[@]} ];
then
# note the space before -1
printf "%s\n" "${i##*[^0-9]}"
else
printf "%s" "${i:0:1}"
fi
counter=$(($counter + 1))
done
A maneira como isso funciona é assim:
- dada uma string na linha de comando como argumento, definimos o separador de campo interno como
/
e permitimos que o bash execute a divisão de palavras para dividir a string em uma matriz chamada items
- iteramos todos os itens da matriz
${items[@]}
, enquanto controlamos o item em que estamos usando a variável do contador e sabemos o número de itens na matriz (a ${#items[@]}
part).
- O
if-statement
é o que nos permite escolher um caractere específico de cada item. Usando expansão de parâmetro, primeiro caractere via ${i:0:1}
. Usando a remoção de prefixo mais longa ${variable##prefix}
, removemos todos os caracteres não dígitos da última string em printf "%s\n" "${i##*[^0-9]}"
.
Aqui está em ação:
$ ./shorten_string.sh "wva/sia/e1"
ws1
$ ./shorten_string.sh "bct/e2"
b2
$ ./shorten_string.sh "sv/de/e11"
sd11