String mit IFS teilen

8

Ich habe ein Beispielskript geschrieben, um die Zeichenfolge zu teilen, aber es funktioniert nicht wie erwartet

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
#split 17.0.0 into NUM
IFS='.' read -a array <<<${ADDR[3]};
for element in "${array[@]}"
do
    echo "Num:$element"
done

Ausgabe

One
XX
X
17.0.0
17 0 0

aber ich erwartete die Ausgabe zu sein:

      One
      XX
      X
      17.0.0
      17
      0
      0
user112232
quelle
Übrigens, wenn eine der folgenden Antworten Ihr Problem gelöst hat, nehmen Sie sich bitte einen Moment Zeit und akzeptieren Sie es, indem Sie auf das Häkchen links klicken. Dadurch wird die Frage als beantwortet markiert und auf die Stack Exchange-Websites wird der Dank ausgesprochen.
Terdon

Antworten:

2

Fix (siehe auch S. Chazelas 'Antwort für den Hintergrund) mit vernünftiger Ausgabe:

#!/bin/bash
IN="One-XX-X-17.0.0"
IFS='-' read -r -a ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    if [ "$i" = "${i//.}" ] ; then 
        echo "Element:$i" 
        continue
    fi
    # split 17.0.0 into NUM
    IFS='.' read -a array <<< "$i"
    for element in "${array[@]}" ; do
        echo "Num:$element"
    done
done

Ausgabe:

Element:One
Element:XX
Element:X
Num:17
Num:0
Num:0

Anmerkungen:

  • Es ist besser, die bedingte 2. Schleife in die 1. Schleife zu setzen.

  • bashpattern substitution ( "${i//.}") prüft, ob .ein Element ein enthält. (Eine caseAnweisung könnte einfacher sein, wenn auch dem OP -Code weniger ähnlich .)

  • reading $arraydurch Eingabe <<< "${ADDR[3]}"weniger allgemein als <<< "$i". Sie müssen nicht wissen, welches Element das .s hat.

  • Der Code geht davon aus, dass das Drucken von " Element: 17.0.0 " nicht beabsichtigt ist. Wenn dieses Verhalten ist beabsichtigt, ersetzen Sie die Hauptschleife mit:

    for i in "${ADDR[@]}"; do
       echo "Element:$i" 
       if [ "$i" != "${i//.}" ] ; then 
       # split 17.0.0 into NUM
           IFS='.' read -a array <<< "$i"
           for element in "${array[@]}" ; do
               echo "Num:$element"
           done
       fi
    done
agc
quelle
1
case $i in (*.*) ...wäre ein kanonischer Weg , dies zu überprüfen , $ienthält .(und auch tragbar sh). Wenn Sie in Kshismen sind, sehen Sie auch:[[ $i = *.* ]]
Stéphane Chazelas
@ StéphaneChazelas, bereits casein den Anmerkungen am Ende erwähnt, aber wir sind uns einig. (Da das OP sowohl <<<als auch Arrays verwendet , ist dies keine große shFrage.)
Agc
10

In alten Versionen mussten bashSie danach Variablen zitieren <<<. Das wurde in 4.4 behoben. In älteren Versionen wurde die Variable in IFS aufgeteilt und die resultierenden Wörter im Leerzeichen verbunden, bevor sie in der temporären Datei gespeichert wurden, aus der diese <<<Umleitung besteht.

In Version 4.2 und früher, wenn Buildins wie readoder umgeleitet werden command, würde diese Aufteilung sogar das IFS für dieses Builtin erfordern (4.3 hat das behoben):

$ bash-4.2 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a b c d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. cat <<< $a'
a.b.c.d
$ bash-4.2 -c 'a=a.b.c.d; IFS=. command cat <<< $a'
a b c d

Das in 4.3 festgelegte:

$ bash-4.3 -c 'a=a.b.c.d; IFS=. read x <<< $a; echo  "$x"'
a.b.c.d

Aber $aunterliegt dort immer noch der Wortspaltung:

$ bash-4.3 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a b c d

In 4.4:

$ bash-4.4 -c 'a=a.b.c.d; IFS=.; read x <<< $a; echo  "$x"'
a.b.c.d

Geben Sie für die Portabilität auf ältere Versionen Ihre Variable an (oder verwenden Sie, zshwoher das <<<kommt und wo dieses Problem nicht auftritt).

$ bash-any-version -c 'a=a.b.c.d; IFS=.; read x <<< "$a"; echo "$x"'
a.b.c.d

Beachten Sie, dass dieser Ansatz zum Teilen einer Zeichenfolge nur für Zeichenfolgen funktioniert, die keine Zeilenumbrüche enthalten. Beachten Sie auch , dass a..b.c.wäre aufgeteilt in "a", "", "b", "c"(kein letztes Element leer).

Um beliebige Zeichenfolgen zu teilen, können Sie stattdessen den Operator split + glob verwenden (was ihn zum Standard macht und das Speichern des Inhalts einer Variablen in einer temporären Datei wie <<<folgt vermeidet ):

var='a.new
line..b.c.'
set -o noglob # disable glob
IFS=.
set -- $var'' # split+glob
for i do
  printf 'item: <%s>\n' "$i"
done

oder:

array=($var'') # in shells with array support

Das ''soll ein nachfolgendes leeres Element beibehalten, falls vorhanden. Das würde auch ein leeres $varin ein leeres Element aufteilen .

Oder verwenden Sie eine Shell mit einem geeigneten Aufteilungsoperator:

  • zsh::

    array=(${(s:.:)var} # removes empty elements
    array=("${(@s:.:)var}") # preserves empty elements
  • rc::

    array = ``(.){printf %s $var} # removes empty elements
  • fish

    set array (string split . -- $var) # not for multiline $var
Stéphane Chazelas
quelle
1

Mit awk würde es dich eine Zeile kosten:

IN="One-XX-X-17.0.0"

awk -F'[-.]' '{ for(i=1;i<=NF;i++) printf "%s : %s\n",($i~/^[0-9]+$/?"Num":"Element"),$i }' <<<"$IN"
  • -F'[-.]'- Feldtrennzeichen basierend auf mehreren Zeichen, in unserem Fall -und.

Die Ausgabe:

Element : One
Element : XX
Element : X
Num : 17
Num : 0
Num : 0
RomanPerekhrest
quelle
Das gleiche könnte mitIFS=-. read -r a array <<< "$IN"
Stéphane Chazelas
@ StéphaneChazelas, es ist anders. Sie zeigen nur einen Schritt zum Konvertieren eines Strings in ein Array. Aber meine einzeilige Ausgabe widmet sich allen Themen: Aufteilen in Felder, Verarbeiten und Ausgeben. Ich konkurriere nicht mit Ihrer Antwort, sie sind einfach anders
RomanPerekhrest
0

Hier mein Weg:

OIFS=$IFS
IFS='-'
IN="One-XX-X-17.0.0"
ADDR=($IN)
for i in "${ADDR[@]}"; do
 echo "Element:$i"
done
IFS='.'
array=(${ADDR[3]})
for element in "${array[@]}"
do
  echo "Num:$element"
done

Ergebnis wie erwartet:

Num:17
Num:0
Num:0
Tonioc
quelle
Das $INruft den Operator split + glob auf. Hier möchten Sie den Glob-Teil nicht ( IN=*-*-/*-17.0.0zum Beispiel anprobieren ), also möchten Sie dies tun, set -o noglobbevor Sie ihn aufrufen. Siehe meine Antwort für Details.
Stéphane Chazelas
1
Vermeiden Sie im Allgemeinen das "Speichern" IFSund das globale Festlegen. Sie möchten wirklich nur den Wert von IFSfür ändern, wenn $INerweitert wird, und Sie möchten auch nicht, dass die Erweiterung mit dem Pfadnamen für die Erweiterung ausgeführt wird. Außerdem OIFS=$IFSwird nicht zwischen den Fällen unterschieden IFS, in denen eine leere Zeichenfolge festgelegt wurde und in denen sie IFSvollständig deaktiviert wurden.
Chepner