Comment extraire les deux premiers caractères d'une chaîne en script shell?


123

Par exemple, étant donné:

USCAGoleta9311734.5021-120.1287855805

Je souhaite extraire uniquement:

US

6
Merci tout le monde. J'ai fini par utiliser 'cut -c1-2', honnêtement, je ne savais même pas que 'cut' était là. Je voudrais dire que je suis assez expérimenté en ligne de commande - mais apparemment j'ai beaucoup à apprendre.
Greg

1
@Greg, sachez simplement que cut est exécuté en tant que processus séparé - il sera plus lent que la solution interne de bash que j'ai postée à côté dans ma réponse. Cela ne fera aucune différence à moins que vous ne traitiez d'énormes ensembles de données, mais vous devez le garder à l'esprit.
paxdiablo

Edit En fait, je pense que cette ligne de code sera probablement exécutée environ 50 000 fois par rapport. Je pourrais donc simplement utiliser la méthode interne Bash - qui, comme vous l'avez dit, permettra d'économiser des ressources indispensables.
Greg

Réponses:


180

La méthode probablement la plus efficace, si vous utilisez le bashshell (et que vous semblez l'être, d'après vos commentaires), est d'utiliser la variante de sous-chaîne de l'expansion des paramètres:

pax> long="USCAGol.blah.blah.blah"
pax> short="${long:0:2}" ; echo "${short}"
US

Il s'agira shortdes deux premiers caractères de long. Si longest plus court que deux caractères,short sera identique.

Cette méthode en shell est généralement meilleure si vous comptez le faire beaucoup (comme 50000 fois par rapport, comme vous le mentionnez) car il n'y a pas de surcharge de création de processus. Toutes les solutions qui utilisent des programmes externes souffriront de cette surcharge.

Si vous souhaitez également garantir une longueur minimale , vous pouvez le compléter au préalable avec quelque chose comme:

pax> long="A"
pax> tmpstr="${long}.."
pax> short="${tmpstr:0:2}" ; echo "${short}"
A.

Cela garantirait que tout élément de moins de deux caractères de longueur soit complété à droite avec des points (ou autre chose, simplement en changeant le caractère utilisé lors de la création tmpstr). Il n'est pas clair que vous en ayez besoin, mais j'ai pensé que je le mettrais pour être complet.


Cela dit, il existe un certain nombre de façons de le faire avec des programmes externes (par exemple si vous n'en avez pas bashà votre disposition), dont certaines sont:

short=$(echo "${long}" | cut -c1-2)
short=$(echo "${long}" | head -c2)
short=$(echo "${long}" | awk '{print substr ($0, 0, 2)}'
short=$(echo "${long}" | sed 's/^\(..\).*/\1/')

Les deux premiers ( cutet head) sont identiques pour une chaîne sur une seule ligne - ils ne vous renvoient que les deux premiers caractères. Ils diffèrent en ce que cutvous donnera les deux premiers caractères de chaque ligne ethead vous donnera les deux premiers caractères de toute l'entrée

Le troisième utilise la fonction de awksous-chaîne pour extraire les deux premiers caractères et le quatrième utilise des sedgroupes de capture (en utilisant ()et \1) pour capturer les deux premiers caractères et remplacer la ligne entière par eux. Ils sont tous deux similaires à cut- ils fournissent les deux premiers caractères de chaque ligne dans l'entrée.

Rien de tout cela n'a d'importance si vous êtes sûr que votre entrée est une seule ligne, ils ont tous un effet identique.


Je préfère utiliser printf '%s'au echocas où il y aurait des caractères étranges dans la chaîne: stackoverflow.com/a/40423558/895245 Pour les obsédés de POSIX: head -cn'est pas POSIX, cut -cet ne awk substrsont sed \1pas sûrs.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli 新疆 改造 中心 996ICU 六四 事件 en utilisant printf, vous n'avez même pas besoin d'un programme supplémentaire. Voyez ma réponse .
bschlueter

60

Le moyen le plus simple est

${string:position:length}

Où cela extrait la $lengthsous-chaîne à partir $stringde $position.

C'est un bash intégré, donc awk ou sed n'est pas nécessaire.


C'est le moyen le plus court, le plus simple et le plus simple d'obtenir la sous-chaîne.
ani627 le

34

Vous avez obtenu plusieurs bonnes réponses et j'irais avec le Bash intégré moi-même, mais depuis que vous avez posé des questions sur sedet awket ( presque ) personne d'autre n'a proposé de solutions basées sur elles, je vous propose celles-ci:

echo "USCAGoleta9311734.5021-120.1287855805" | awk '{print substr($0,0,2)}'

et

echo "USCAGoleta9311734.5021-120.1287855805" | sed 's/\(^..\).*/\1/'

L' awkun devrait être assez évident, mais voici une explication de l' sedun:

  • remplacer "s /"
  • le groupe "()" de deux caractères quelconques ".." commençant au début de la ligne "^" et suivi de n'importe quel caractère "." répété zéro fois ou plus "*" (les contre-obliques sont nécessaires pour échapper à certains caractères spéciaux)
  • par "/" le contenu du premier (et seul, dans ce cas) groupe (ici la barre oblique inverse est un échappement spécial faisant référence à une sous-expression correspondante)
  • terminé "/"

1
Dans awk, les chaînes commencent à l'index 1, vous devez donc utiliser substr($0,1,2).
Isaac le

8

Si vous êtes bash, vous pouvez dire:

bash-3.2$ var=abcd
bash-3.2$ echo ${var:0:2}
ab

C'est peut-être exactement ce dont vous avez besoin…


la réponse la plus simple et la plus simple! travaillé comme un charme
aloha

7

Juste grep:

echo 'abcdef' | grep -Po "^.."        # ab

Répond à mes besoins. Vous pouvez supprimer l' -Poption pour la raccourcir. Toutes les expressions régulières comprendront ce modèle.
datashaman

6

Vous pouvez utiliser printf:

$ original='USCAGoleta9311734.5021-120.1287855805'
$ printf '%-.2s' "$original"
US

5

colrm - supprime les colonnes d'un fichier

Pour laisser les deux premiers caractères, supprimez simplement les colonnes à partir de 3

cat file | colrm 3

4

Assez tard en effet mais la voici

sed 's/.//3g'

Ou

awk NF=1 FPAT=..

Ou

perl -pe '$_=unpack a2'

2

Si vous souhaitez utiliser des scripts shell et ne pas compter sur des extensions non-posix (telles que les soi-disant bashismes), vous pouvez utiliser des techniques qui ne nécessitent pas de forger des outils externes tels que grep, sed, cut, awk, etc., qui alors rendre votre script moins efficace. Peut-être que l'efficacité et la portabilité posix ne sont pas importantes dans votre cas d'utilisation. Mais dans le cas où c'est le cas (ou juste comme une bonne habitude), vous pouvez utiliser la méthode d'option d' extension de paramètre suivante pour extraire les deux premiers caractères d'une variable shell:

$ sh -c 'var=abcde; echo "${var%${var#??}}"'
ab

Cela utilise l' expansion des paramètres "plus petit préfixe" pour supprimer les deux premiers caractères (c'est la ${var#??}partie), puis l' expansion des paramètres "plus petit suffixe" (le${var% partie) pour supprimer cette chaîne de caractères tout sauf les deux premiers de l'original valeur.

Cette méthode a été précédemment décrite dans cette réponse à la question "Shell = Vérifier si la variable commence par #". Cette réponse décrit également quelques méthodes d'expansion de paramètres similaires qui peuvent être utilisées dans un contexte légèrement différent de celui qui s'applique à la question d'origine ici.


La meilleure réponse devrait être au top. pas de fourches, pas de bashismes. fonctionne même avec de petites coquilles telles que dash.
exore

1

Si votre système utilise un shell différent (pas bash), mais que votre système l'a fait bash, vous pouvez toujours utiliser la manipulation de chaîne inhérente de bashen appelant bashavec une variable:

strEcho='echo ${str:0:2}' # '${str:2}' if you want to skip the first two characters and keep the rest
bash -c "str=\"$strFull\";$strEcho;"

Cela utilise la même méthode que la réponse principale , n'appelant que bashsi vous ne l'utilisez pas déjà.
palswim

Malheureusement, cela s'accompagne de tous les frais généraux liés à l'invocation d'un autre processus, mais parfois ces frais généraux n'ont pas autant d'importance que la simplicité et la familiarité.
palswim

1

Juste pour le plaisir, j'ajouterai quelques-uns qui, bien qu'ils soient trop compliqués et inutiles, n'ont pas été mentionnés:

head -c 2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

echo 'USCAGoleta9311734.5021-120.1287855805' | dd bs=2 count=1 status=none

sed -e 's/^\(.\{2\}\).*/\1/;' <( echo 'USCAGoleta9311734.5021-120.1287855805')

cut -c 1-2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

python -c "print(r'USCAGoleta9311734.5021-120.1287855805'[0:2])"

ruby -e 'puts "USCAGoleta9311734.5021-120.1287855805"[0..1]'


0

si mystring = USCAGoleta9311734.5021-120.1287855805

print substr(mystring,0,2)

nous imprimerions

où 0 est la position de départ et 2 est comment lire les caractères meny


Dites ... n'est-ce pas GW-BASIC? Oh, attendez, c'est awk. Désolé, je n'ai pas pu le dire au début.
Suspendu jusqu'à nouvel ordre.

0

C'est ce que vous recherchez?

my $string = 'USCAGoleta9311734.5021-120.1287855805';

my $first_two_chars = substr $string, 0, 2;

ref: substr


1
étant donné qu'il / elle est susceptible d'appeler cela depuis le shell, une meilleure forme seraitperl -e 'print substr $ARGV[0], 0, 2' 'USCAGoleta9311734.5021-120.1287855805'
Chas. Owens
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.