Extrair substring no Bash

Dado um nome de arquivo na forma someletters_12345_moreleters.ext , eu quero extrair os 5 dígitos e colocá-los em uma variável.

Então, para enfatizar o ponto, eu tenho um nome de arquivo com um número x de caracteres, em seguida, uma seqüência de cinco dígitos cercada por um único sublinhado em cada lado, em seguida, outro conjunto de x número de caracteres. Eu quero pegar o número de 5 dígitos e colocar isso em uma variável.

Estou muito interessado no número de maneiras diferentes que isso pode ser realizado.

Use o corte :

 echo 'someletters_12345_moreleters.ext' | cut -d'_' -f 2 

Mais genérico:

 INPUT='someletters_12345_moreleters.ext' SUBSTRING=$(echo $INPUT| cut -d'_' -f 2) echo $SUBSTRING 

Se x é constante, a expansão de parâmetro a seguir executa a extração de substring:

 b=${a:12:5} 

onde 12 é o deslocamento (baseado em zero) e 5 é o comprimento

Se os sublinhados em torno dos dígitos forem os únicos na input, você poderá remover o prefixo e o sufixo (respectivamente) em duas etapas:

 tmp=${a#*_} # remove prefix ending in "_" b=${tmp%_*} # remove suffix starting with "_" 

Se houver outros sublinhados, provavelmente é viável de qualquer maneira, embora seja mais complicado. Se alguém souber como executar as duas expansões em uma única expressão, também gostaria de saber.

Ambas as soluções apresentadas são puras, sem a desova do processo envolvida, portanto, muito rápido.

Solução genérica em que o número pode estar em qualquer lugar no nome do arquivo, usando a primeira dessas sequências:

 number=$(echo $filename | egrep -o '[[:digit:]]{5}' | head -n1) 

Outra solução para extrair exatamente uma parte de uma variável:

 number=${filename:offset:length} 

Se o seu nome de arquivo sempre tiver o formato stuff_digits_... você pode usar o awk:

 number=$(echo $filename | awk -F _ '{ print $2 }') 

Ainda outra solução para remover tudo, exceto dígitos, use

 number=$(echo $filename | tr -cd '[[:digit:]]') 

tente usar o cut -c startIndx-stopIndx

Caso alguém queira informações mais rigorosas, você também pode pesquisar no homem bash assim

 $ man bash [press return key] /substring [press return key] [press "n" key] [press "n" key] [press "n" key] [press "n" key] 

Resultado:

 $ {parameter: offset}
        $ {parameter: offset: length}
               Expansão de Substring.  Expande até caracteres de comprimento de
               parâmetro iniciando no caractere especificado por deslocamento.  E se
               length is omitted, expande-se para a substring do parâmetro start-
               no caractere especificado por deslocamento.  comprimento e deslocamento são
               expressões aritméticas (ver AVALIAÇÃO ARITMÉTICA abaixo).  E se
               offset avalia para um número menor que zero, o valor é usado
               como um deslocamento do final do valor do parâmetro.  Aritmética
               expressões começando com a - devem ser separadas por espaços em branco
               do anterior: para ser distinguido do padrão de uso
               Expansão de valores.  Se o comprimento for avaliado em um número menor que
               zero, e parâmetro não é @ e não é um indexado ou associativo
               array, ele é interpretado como um deslocamento do final do valor
               de parâmetro em vez de um número de caracteres, e a expansão
               sion é os caracteres entre os dois offsets.  Se o parâmetro é
               @, o resultado é o comprimento dos parâmetros posicionais começando em off-
               conjunto.  Se parameter é um nome de array indexado subscrito por @ ou
               *, o resultado é o comprimento dos membros da matriz começando com
               $ {parameter [offset]}.  Uma compensação negativa é tomada em relação a
               um maior que o índice máximo da matriz especificada.  Sub-
               A expansão de string aplicada a um array associativo produz efeitos
               resultados multados.  Observe que um deslocamento negativo deve ser separado
               do cólon por pelo menos um espaço para evitar confusões
               com o: - expansão.  A indexação da subcadeia é baseada em zero, a menos que
               são utilizados os parâmetros posicionais, caso em que a indexação
               começa em 1 por padrão.  Se o deslocamento for 0 e o valor posicional
               parâmetros são usados, $ 0 é prefixado na lista.

Com base na resposta do jor (que não funciona para mim):

 substring=$(expr "$filename" : '.*_\([^_]*\)_.*') 

Estou surpreso que esta solução pura não surgiu:

 a="someletters_12345_moreleters.ext" IFS="_" set $a echo $2 # prints 12345 

Você provavelmente deseja redefinir o IFS para o valor que era antes, ou unset IFS posteriormente!

Seguindo os requisitos

Eu tenho um nome de arquivo com um número x de caracteres, em seguida, uma seqüência de cinco dígitos rodeada por um único sublinhado em ambos os lados, em seguida, outro conjunto de x número de caracteres. Eu quero pegar o número de 5 dígitos e colocar isso em uma variável.

Eu encontrei algumas formas grep que podem ser úteis:

 $ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]+" 12345 

ou melhor

 $ echo "someletters_12345_moreleters.ext" | grep -Eo "[[:digit:]]{5}" 12345 

E então com a syntax -Po :

 $ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+' 12345 

Ou se você quiser encheckboxr exatamente 5 caracteres:

 $ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}' 12345 

Finalmente, para torná-lo armazenado em uma variável, basta usar a syntax var=$(command) .

Sem quaisquer subprocesss, você pode:

 shopt -s extglob front=${input%%_+([a-zA-Z]).*} digits=${front##+([a-zA-Z])_} 

Uma variante muito pequena disso também funcionará em ksh93.

Se nos concentrarmos no conceito de:
“Uma corrida de (um ou vários) dígitos”

Poderíamos usar várias ferramentas externas para extrair os números.
Poderíamos facilmente apagar todos os outros caracteres, seja sed ou tr:

 name='someletters_12345_moreleters.ext' echo $name | sed 's/[^0-9]*//g' # 12345 echo $name | tr -c -d 0-9 # 12345 

Mas se $ name contiver várias execuções de números, o acima irá falhar:

Se “name = someletters_12345_moreleters_323_end.ext”, então:

 echo $name | sed 's/[^0-9]*//g' # 12345323 echo $name | tr -c -d 0-9 # 12345323 

Precisamos usar expresões regulares (regex).
Para selecionar apenas a primeira execução (12345 não 323) em sed e perl:

 echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$/\1/' perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";' 

Mas também podemos fazê-lo diretamente no bash (1) :

 regex=[^0-9]*([0-9]{1,}).*$; \ [[ $name =~ $regex ]] && echo ${BASH_REMATCH[1]} 

Isso nos permite extrair a PRIMEIRA execução de dígitos de qualquer comprimento
rodeado por qualquer outro texto / caracteres.

Nota : regex=[^0-9]*([0-9]{5,5}).*$; irá corresponder apenas a execuções de 5 dígitos. 🙂

(1) : mais rápido do que chamar uma ferramenta externa para cada texto curto. Não é mais rápido do que fazer todo o processamento dentro do sed ou awk para arquivos grandes.

Aqui está uma solução de sufixo-prefixo (semelhante às soluções dadas por JB e Darron) que corresponde ao primeiro bloco de dígitos e não depende dos sublinhados circundantes:

 str='someletters_12345_morele34ters.ext' s1="${str#"${str%%[[:digit:]]*}"}" # strip off non-digit prefix from str s2="${s1%%[^[:digit:]]*}" # strip off non-digit suffix from s1 echo "$s2" # 12345 

Aqui está como eu faria:

 FN=someletters_12345_moreleters.ext [[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]} 

Nota: o acima é uma expressão regular e é restrito ao seu cenário específico de cinco dígitos cercados por sublinhados. Altere a expressão regular se você precisar de correspondência diferente.

Eu amo a capacidade do sed de lidar com grupos regex:

 > var="someletters_12345_moreletters.ext" > digits=$( echo $var | sed "s/.*_\([0-9]\+\).*/\1/p" -n ) > echo $digits 12345 

Uma opção um pouco mais geral seria não assumir que você tem um sublinhado _ marcando o início de sua sequência de dígitos, portanto, por exemplo, removendo todos os não-números que você obtém antes de sua sequência: s/[^0-9]\+\([0-9]\+\).*/\1/p .


 > man sed | grep s/regexp/replacement -A 2 s/regexp/replacement/ Attempt to match regexp against the pattern space. If successful, replace that portion matched with replacement. The replacement may contain the special character & to refer to that portion of the pattern space which matched, and the special escapes \1 through \9 to refer to the corresponding matching sub-expressions in the regexp. 

Mais sobre isso, caso você não esteja muito confiante com regexps:

  • s é para _substituto
  • [0-9]+ corresponde a 1 ou mais dígitos
  • \1 links para o grupo n.1 da saída regex (o grupo 0 é a correspondência completa, o grupo 1 é a correspondência entre parênteses neste caso)
  • p flag é para _p_rinting

Todos os escapes \ estão lá para fazer o trabalho de processamento regular do sed .

Dado test.txt é um arquivo contendo “ABCDEFGHIJKLMNOPQRSTUVWXYZ”

 cut -b19-20 test.txt > test1.txt # This will extract chars 19 & 20 "ST" while read -r; do; > x=$REPLY > done < test1.txt echo $x ST 

semelhante ao substr (‘abcdefg’, 2-1, 3) em php:

 echo 'abcdefg'|tail -c +2|head -c 3 

Minha resposta terá mais controle sobre o que você quer da sua string. Aqui está o código de como você pode extrair 12345 da sua string

 str="someletters_12345_moreleters.ext" str=${str#*_} str=${str%_more*} echo $str 

Isso será mais eficiente se você quiser extrair algo que tenha caracteres como abc ou qualquer caractere especial como _ ou - . Por exemplo: Se sua string é assim e você quer tudo que é depois de someletters_ e before _moreleters.ext :

 str="someletters_123-45-24a&13b-1_moreleters.ext" 

Com o meu código você pode mencionar exatamente o que você quer. Explicação:

#* Ele removerá a string anterior, incluindo a chave correspondente. Aqui a chave que mencionamos é _ % Ela irá remover a seguinte string incluindo a chave correspondente. Aqui a chave que mencionamos é ‘_mais *’

Faça algumas experiências você mesmo e você acharia isso interessante.

Há também o comando bash builtin ‘expr’:

 INPUT="someletters_12345_moreleters.ext" SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' ` echo $SUBSTRING 

Ok, aqui vai pura Substituição de Parâmetro com uma string vazia. Ressalva é que eu defini someletters e moreletters como apenas personagens. Se eles forem alfanuméricos, isso não funcionará como está.

 filename=someletters_12345_moreletters.ext substring=${filename//@(+([az])_|_+([az]).*)} echo $substring 12345 

Um pouco atrasado, mas acabei de encontrar este problema e encontrei o seguinte:

 host:/tmp$ asd=someletters_12345_moreleters.ext host:/tmp$ echo `expr $asd : '.*_\(.*\)_'` 12345 host:/tmp$ 

Eu usei para obter resolução de milissegundos em um sistema embarcado que não possui% N para data:

 set `grep "now at" /proc/timer_list` nano=$3 fraction=`expr $nano : '.*\(...\)......'` $debug nano is $nano, fraction is $fraction 

Uma solução bash:

 IFS="_" read -rx digs x <<<'someletters_12345_moreleters.ext' 

Isso vai atrapalhar uma variável chamada x . O var x pode ser alterado para o var _ .

 input='someletters_12345_moreleters.ext' IFS="_" read -r _ digs _ <<<"$input"