web-dev-qa-db-fra.com

Extraire la sous-chaîne dans Bash

Étant donné un nom de fichier sous la forme someletters_12345_moreleters.ext, je veux extraire les 5 chiffres et les mettre dans une variable.

Donc, pour souligner le point, j'ai un nom de fichier avec x nombre de caractères, puis une séquence de cinq chiffres entourée d'un seul trait de soulignement de part et d'autre, puis un autre ensemble de x nombre de caractères. Je veux prendre le nombre à 5 chiffres et le mettre dans une variable.

Je suis très intéressé par le nombre de façons différentes dont cela peut être accompli.

650
Berek Bryan

Utilisez couper :

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

Plus générique:

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

Si x est constant, le développement de paramètre suivant effectue l'extraction de sous-chaîne:

b=${a:12:5}

12 est le décalage (base zéro) et 5 est la longueur

Si les caractères soulignés autour des chiffres sont les seuls de l'entrée, vous pouvez supprimer le préfixe et le suffixe (respectivement) en deux étapes:

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

S'il y a d'autres soulignés, c'est probablement réalisable, bien que plus délicat. Si quelqu'un sait comment effectuer les deux extensions en une seule expression, j'aimerais aussi le savoir.

Les deux solutions présentées sont purement bash, sans génération de processus, donc très rapide.

990
JB.

Solution générique où le numéro peut être n'importe où dans le nom du fichier, en utilisant la première de ces séquences:

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

Une autre solution pour extraire exactement une partie d'une variable:

number=${filename:offset:length}

Si votre nom de fichier a toujours le format stuff_digits_..., vous pouvez utiliser awk:

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

Encore une autre solution pour tout supprimer sauf les chiffres, utilisez

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

essayez juste d'utiliser cut -c startIndx-stopIndx

82
brown.2179

Si quelqu'un souhaite des informations plus rigoureuses, vous pouvez également les rechercher dans man bash comme ceci

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

Résultat:

 $ {paramètre: offset} 
 $ {paramètre: offset: longueur} 
 Extension de la sous-chaîne. Augmente la longueur des caractères du paramètre 
 À partir du caractère spécifié par offset. Si 
 Longueur est omis, passe à la sous-chaîne du paramètre start - 
 Ing au caractère spécifié par offset. longueur et offset sont des expressions arithmétiques de 
 (voir EVALUATION ARITHMÉTIQUE ci-dessous). Si 
 Correspond à un nombre inférieur à zéro, la valeur est utilisée 
 Comme un décalage par rapport à la fin de la valeur du paramètre. Les expressions arithmétiques 
 Commençant par a - doivent être séparées par les espaces 
 Des précédentes: à distinguer du développement Utiliser les valeurs par défaut 
. Si la longueur est évaluée à un nombre inférieur à 
 Zéro et que le paramètre n'est pas @ et n'est pas un tableau indexé ou associatif 
, Il est interprété comme un décalage par rapport à la fin de la valeur 
 paramètre plutôt qu’un nombre de caractères, et l’expan - 
 sion correspond aux caractères entre les deux décalages. Si le paramètre est 
 @, Le résultat est le paramètre de position de longueur commençant à off - 
 Réglé. Si paramètre est un nom de tableau indexé ayant comme indice @ ou 
 *, Le résultat correspond à la longueur des membres du tableau commençant par 
 $ {Paramètre [offset]}. Un décalage négatif est pris par rapport à 
 Supérieur à l'indice maximal du tableau spécifié. Le développement de chaînes sous - 
 Appliqué à un tableau associatif produit des résultats précis sous - 
. Notez qu'un décalage négatif doit être séparé 
 Des deux points par au moins un espace pour éviter d'être confondu 
 Avec: - l'expansion. L'indexation des sous-chaînes est basée sur zéro, à moins que 
 Ne soit utilisé comme paramètre de position. Dans ce cas, l'indexation 
 Commence à 1 par défaut. Si offset est égal à 0 et que les paramètres de position 
 Sont utilisés, $ 0 est préfixé de la liste.
33
jperelli

Construire sur la réponse de Jor (qui ne fonctionne pas pour moi):

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

Je suis surpris que cette solution pure bash ne soit pas apparue:

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

Vous voulez probablement réinitialiser IFS à quelle valeur il était avant, ou unset IFS après!

19
user1338062

Voici comment je le ferais:

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

Remarque: ce qui précède est une expression régulière et est limité à votre scénario spécifique à cinq chiffres entouré de traits de soulignement. Modifiez l'expression régulière si vous avez besoin d'une correspondance différente.

13
nicerobot

Suivre les exigences

J'ai un nom de fichier avec x nombre de caractères, puis une séquence de cinq chiffres entourée d'un trait de soulignement unique de chaque côté, puis une autre série de x nombres de caractères. Je veux prendre le nombre à 5 chiffres et le mettre dans une variable.

J'ai trouvé des moyens grep qui peuvent être utiles:

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

ou mieux

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

Et puis avec la syntaxe -Po:

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

Ou si vous voulez faire correspondre exactement 5 caractères:

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

Enfin, pour le stocker dans une variable, il suffit d’utiliser la syntaxe var=$(command).

12
fedorqui

Sans aucun sous-processus, vous pouvez:

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

Une très petite variante de cela fonctionnera également dans ksh93.

10
Darron

Si nous nous concentrons sur le concept de:
"Une série de (un ou plusieurs) chiffres"

Nous pourrions utiliser plusieurs outils externes pour extraire les chiffres.
Nous pourrions très facilement effacer tous les autres caractères, sed ou tr:

name='someletters_12345_moreleters.ext'

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

Mais si $ name contient plusieurs suites de nombres, ceci échouera:

Si "name = someletters_12345_moreleters_323_end.ext", alors:

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

Nous devons utiliser des expressions régulières (regex).
Pour ne sélectionner que la première exécution (12345 pas 323) dans sed et Perl:

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

Mais nous pourrions aussi bien le faire directement en bash(1) :

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

Cela nous permet d'extraire la première série de chiffres de n'importe quelle longueur
entouré de tout autre texte/caractère.

Note: regex=[^0-9]*([0-9]{5,5}).*$; ne correspondra qu'à des séries de 5 chiffres exactement. :-)

(1): plus rapide que d'appeler un outil externe pour chaque texte court. Pas plus rapide que de faire tous les traitements à l'intérieur de sed ou awk pour les gros fichiers.

10
user2350426

Voici une solution préfixe-suffixe (similaire aux solutions données par JB et Darron) qui correspond au premier bloc de chiffres et ne dépend pas des caractères de soulignement environnants:

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
9
codist

Étant donné que test.txt est un fichier contenant "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
6
Rick Osman

J'aime la capacité de sed à gérer les groupes de regex:

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

Une option légèrement plus générale serait pas de supposer que vous avez un trait de soulignement _ marquant le début de votre séquence de chiffres, par exemple en supprimant tous les non-chiffres que vous obtenez avant votre séquence: 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.

Plus à ce sujet, au cas où vous n'êtes pas trop confiant avec les expressions rationnelles:

  • s est pour _substitute
  • [0-9]+ correspond à 1+ chiffres
  • \1 est lié au groupe n.1 de la sortie regex (le groupe 0 correspond à la correspondance complète, le groupe 1 à la correspondance entre parenthèses dans ce cas).
  • p le drapeau est pour _p_rinting

Tous les échappements \ sont là pour faire fonctionner le traitement des expressions rationnelles de sed.

6
Campa

Ma réponse aura plus de contrôle sur ce que vous voulez sortir de votre chaîne. Voici le code sur comment extraire 12345 de votre chaîne

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

Cela sera plus efficace si vous voulez extraire quelque chose qui a des caractères comme abc ou des caractères spéciaux tels que _ ou -. Par exemple: Si votre chaîne est comme ceci et que vous voulez tout ce qui se trouve après someletters_ et avant _moreleters.ext:

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

Avec mon code, vous pouvez mentionner ce que vous voulez exactement. Explication:

#* Il supprimera la chaîne précédente, y compris la clé correspondante. Ici, la clé mentionnée est _% Elle supprimera la chaîne suivante, y compris la clé correspondante. Ici, la clé mentionnée est '_more *'

Faites des expériences vous-même et vous trouverez cela intéressant.

3

Ok, voici la substitution de paramètres pure avec une chaîne vide. La mise en garde est que j'ai défini someletters et moreletters comme caractères uniquement. S'ils sont alphanumériques, cela ne fonctionnera pas tel quel.

filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
2
morbeo

similaire à substr ('abcdefg', 2-1, 3) en php:

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

Il y a aussi la commande 'expr' de Bash intégrée:

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

Un peu tard, mais je viens de rencontrer ce problème et trouve ce qui suit:

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

Je l'ai utilisé pour obtenir une résolution en millisecondes sur un système intégré qui n'a pas% N pour la date:

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

Une solution bash:

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

Cela encombrera une variable appelée x. La var x peut être changée en var _.

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