Substrat d'extraction en Bash
donné un nom de fichier dans 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 à cinq chiffres entourée d'un underscore simple de chaque côté 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 que cela peut être accompli.
20 réponses
si x est constant, le paramètre d'expansion suivant effectue l'extraction sur substrat:
b=${a:12:5}
où 12 est l'offset (base zéro) et 5 est la longueur
si les soulignements autour des chiffres sont les seuls dans l'entrée, vous pouvez enlever 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 souligne, c'est probablement faisable de toute façon, bien que plus délicat. Si quelqu'un sait réaliser les deux extensions en une seule expression, j'aimerais le savoir aussi.
les deux solutions présentées sont pur bash, sans processus de fraie impliqué, donc très rapide.
solution générique où le numéro peut se trouver 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 }')
encore une autre solution pour supprimer tout sauf les chiffres, utiliser
number=$(echo $filename | tr -cd '[[:digit:]]')
dans le cas où quelqu'un veut des informations plus rigoureuses, vous pouvez également le 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:
${parameter:offset} ${parameter:offset:length} Substring Expansion. Expands to up to length characters of parameter starting at the character specified by offset. If length is omitted, expands to the substring of parameter start‐ ing at the character specified by offset. length and offset are arithmetic expressions (see ARITHMETIC EVALUATION below). If offset evaluates to a number less than zero, the value is used as an offset from the end of the value of parameter. Arithmetic expressions starting with a - must be separated by whitespace from the preceding : to be distinguished from the Use Default Values expansion. If length evaluates to a number less than zero, and parameter is not @ and not an indexed or associative array, it is interpreted as an offset from the end of the value of parameter rather than a number of characters, and the expan‐ sion is the characters between the two offsets. If parameter is @, the result is length positional parameters beginning at off‐ set. If parameter is an indexed array name subscripted by @ or *, the result is the length members of the array beginning with ${parameter[offset]}. A negative offset is taken relative to one greater than the maximum index of the specified array. Sub‐ string expansion applied to an associative array produces unde‐ fined results. Note that a negative offset must be separated from the colon by at least one space to avoid being confused with the :- expansion. Substring indexing is zero-based unless the positional parameters are used, in which case the indexing starts at 1 by default. If offset is 0, and the positional parameters are used, "151910920" is prefixed to the list.
construit sur la réponse de jor (qui ne fonctionne pas pour moi):
substring=$(expr "$filename" : '.*_\([^_]*\)_.*')
je suis surpris que cette pure solution de bash ne soit pas venue:
a="someletters_12345_moreleters.ext"
IFS="_"
set $a
echo
# prints 12345
vous voulez probablement réinitialiser IFS à sa valeur d'avant, ou unset IFS
après!
selon les prescriptions
j'ai un nom de fichier avec x nombre de caractères puis un cinq chiffres séquence entouré par un seul trait de soulignement de chaque côté puis de l'autre ensemble de x nombre de caractères. Je veux prendre le numéro à 5 chiffres et mettre dans une variable.
j'ai trouvé quelques grep
les moyens 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 ensuite avec -Po
syntaxe:
$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d+'
12345
ou si vous voulez le faire correspondre exactement 5 caractères:
$ echo "someletters_12345_moreleters.ext" | grep -Po '(?<=_)\d{5}'
12345
Enfin, pour qu'il soit stockée dans une variable, il suffit d'utiliser la "151960920 de syntaxe".
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.
Si nous nous concentrons sur le concept de:
"Une course de (un ou plusieurs) chiffres"
Nous pourrions utiliser plusieurs outils externes pour extraire les numéros.
Nous pourrions facilement effacer tous les autres caractères, soit 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 tirages de nombres, ce qui précède échouera:
si "nom=someletters_12345_moreleters_323_end.ext", puis:
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 sélectionner seulement la première exécution (12345 pas 323) dans sed et perl:
echo $name | sed 's/[^0-9]*\([0-9]\{1,\}\).*$//'
perl -e 'my $name='$name';my ($num)=$name=~/(\d+)/;print "$num\n";'
mais nous pourrions aussi bien le faire directement à 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é par tout autre texte/caractères.
Note : regex=[^0-9]*([0-9]{5,5}).*$;
ne correspond qu'à des tirages à 5 chiffres. :- )
(1) : plus rapide que d'appeler un outil externe pour chaque texte court. Pas plus vite que de faire tout le traitement à l'intérieur de sed ou awk pour les gros fichiers.
Voici une solution préfixe-suffixe (semblable aux solutions données par JB et Darron) qui correspond au premier bloc de chiffres et ne dépend pas des soulignements 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
Voici comment je ferais:
FN=someletters_12345_moreleters.ext
[[ $FN =~ _([[:digit:]]{5})_ ]] && NUM=${BASH_REMATCH[1]}
Note: ce qui précède est une expression régulière et se limite à votre scénario spécifique de cinq chiffres entourés de underscores. Changez l'expression régulière si vous avez besoin d'une correspondance différente.
j'aime sed
's la capacité de traiter avec la regex groupes:
> var="someletters_12345_moreletters.ext"
> digits=$( echo $var | sed "s/.*_\([0-9]\+\).*//p" -n )
> echo $digits
12345
une option légèrement plus générale serait et non pour supposer que vous avez un underscore _
marquant le début de votre séquence de chiffres, donc par exemple en enlevant tous les non-nombres que vous obtenez avant votre séquence: s/[^0-9]\+\([0-9]\+\).*//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 through to refer to the corresponding matching sub-expressions in the regexp.
plus sur ce, en cas où vous n'êtes pas trop confiant avec regexps:
-
s
est pour _s_ubstitute -
[0-9]+
correspond à 1 + chiffres -
liens pour le groupe n.1 de la regex de sortie (groupe 0 est l'ensemble du match, le groupe 1 est le match à l'intérieur de parenthèses dans ce cas)
-
p
le drapeau est pour _p_rinting
toutes les évasions \
sont là pour faire le travail de traitement regexp de sed
.
à l'essai.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
similaire à substr ('abcdefg', 2-1, 3) en php:
echo 'abcdefg'|tail -c +2|head -c 3
ma réponse aura plus de contrôle sur ce que vous voulez de votre chaîne. Voici le code sur comment vous pouvez extraire 12345
de votre chaîne
str="someletters_12345_moreleters.ext"
str=${str#*_}
str=${str%_more*}
echo $str
Ce sera plus efficace si vous voulez extraire quelque chose qui a des caractères comme abc
ou de caractères spéciaux comme _
ou -
. Par exemple: si votre chaîne est comme ceci et que vous voulez tout ce qui est après someletters_
et avant _moreleters.ext
:
str="someletters_123-45-24a&13b-1_moreleters.ext"
avec mon code, vous pouvez dire exactement ce que vous voulez. Explication:
#*
il supprimera la chaîne précédente y compris la clé correspondante. Ici, la clé que nous avons mentionnée est _
%
il supprimera la chaîne suivante, y compris la clé correspondante. Ici, la clé que nous avons mentionnée est '_more*'
faites quelques expériences vous-même et vous trouverez cela intéressant.
il y a aussi la commande bash builtin 'expr':
INPUT="someletters_12345_moreleters.ext"
SUBSTRING=`expr match "$INPUT" '.*_\([[:digit:]]*\)_.*' `
echo $SUBSTRING
Ok, voici la pure Substitution de paramètre avec une chaîne vide. La mise en garde est que j'ai défini someletters et moreletters comme des caractères seulement. Si ils sont alphanumériques, cela ne fonctionne pas comme il est.
filename=someletters_12345_moreletters.ext
substring=${filename//@(+([a-z])_|_+([a-z]).*)}
echo $substring
12345
un peu tard, mais je viens de courir à travers ce problème et a trouvé 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 de milliseconde sur un système intégré qui n'a pas de %N pour la date:
set `grep "now at" /proc/timer_list`
nano=
fraction=`expr $nano : '.*\(...\)......'`
$debug nano is $nano, fraction is $fraction