bash - beispiele - linux spalten ausgeben




Wie spalte ich eine Zeichenfolge auf einem Trennzeichen in Bash? (20)

Kompatible Antwort

Auf diese SO-Frage gibt es bereits viele verschiedene Möglichkeiten, dies in bash zu tun. Aber Bash hat viele spezielle Eigenschaften, so genannte bashism , die gut funktionieren, aber das wird in keiner anderen shell funktionieren.

Insbesondere Arrays , assoziatives Array und Musterersetzung sind reine Bashismen und funktionieren möglicherweise nicht unter anderen Shells .

Auf meinem Debian GNU / Linux gibt es eine Standard Shell namens dash , aber ich kenne viele Leute, die gerne ksh benutzen.

Schließlich gibt es in einer sehr kleinen Situation ein spezielles Tool namens busybox mit seinem eigenen Shell-Interpreter ( ash ).

Angeforderte Zeichenfolge

Das String-Beispiel in SO-Frage ist:

IN="[email protected];[email protected]"

Da dies bei Leerzeichen nützlich sein könnte und da Leerzeichen das Ergebnis der Routine ändern könnten, verwende ich diesen Beispielstring:

 IN="[email protected];[email protected];Full Name <[email protected]>"

Split-String basierend auf dem Delimiter in bash (Version> = 4.2)

Unter Pure Bash können wir Arrays und IFS verwenden :

var="[email protected];[email protected];Full Name <[email protected]>"

oIFS="$IFS"
IFS=";"
declare -a fields=($var)
IFS="$oIFS"
unset oIFS

IFS=\; read -a fields <<<"$var"

Wenn Sie diese Syntax unter der letzten bash verwenden, ändern Sie $IFS für die aktuelle Sitzung, sondern nur für den aktuellen Befehl:

set | grep ^IFS=
IFS=$' \t\n'

Jetzt wird die String- var aufgeteilt und in einem Array gespeichert (benannte fields ):

set | grep ^fields=\\\|^var=
fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")
var='[email protected];[email protected];Full Name <[email protected]>'

Wir könnten mit declare -p variablen Inhalt anfordern:

declare -p var fields
declare -- var="[email protected];[email protected];Full Name <[email protected]>"
declare -a fields=([0]="[email protected]" [1]="[email protected]" [2]="Full Name <[email protected]>")

read ist der schnellste Weg, um den Split zu machen, da keine Gabeln und keine externen Ressourcen aufgerufen werden.

Von dort können Sie die Syntax verwenden, die Sie bereits für die Verarbeitung jedes Feldes kennen:

for x in "${fields[@]}";do
    echo "> [$x]"
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

oder lasse jedes Feld nach der Verarbeitung fallen (Ich mag diese Verschiebung ):

while [ "$fields" ] ;do
    echo "> [$fields]"
    fields=("${fields[@]:1}")
    done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

oder sogar für den einfachen Ausdruck (kürzere Syntax):

printf "> [%s]\n" "${fields[@]}"
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

Auf String basierende Trennzeichenfolge in der shell

Aber wenn Sie etwas verwendbares unter vielen Shells schreiben, müssen Sie Bashismen nicht verwenden.

Es gibt eine Syntax, die in vielen Shells verwendet wird, um eine Zeichenfolge über das erste oder letzte Vorkommen einer Teilzeichenfolge aufzuteilen:

${var#*SubStr}  # will drop begin of string up to first occur of `SubStr`
${var##*SubStr} # will drop begin of string up to last occur of `SubStr`
${var%SubStr*}  # will drop part of string from last occur of `SubStr` to the end
${var%%SubStr*} # will drop part of string from first occur of `SubStr` to the end

(Das Fehlen dieser ist der Hauptgrund meiner Antwort Veröffentlichung;)

Wie von Score_Under :

# und % löschen die kürzest mögliche übereinstimmende Zeichenkette und

%% und %% löschen die längste mögliche Zeit.

Dieses kleine Beispielskript funktioniert gut unter bash , dash , ksh , busybox und wurde auch unter Mac-OS bash getestet:

var="[email protected];[email protected];Full Name <[email protected]>"
while [ "$var" ] ;do
    iter=${var%%;*}
    echo "> [$iter]"
    [ "$var" = "$iter" ] && \
        var='' || \
        var="${var#*;}"
  done
> [[email protected]]
> [[email protected]]
> [Full Name <[email protected]>]

Habe Spaß!

Ich habe diese Zeichenfolge in einer Variablen gespeichert:

IN="[email protected];[email protected]"

Jetzt möchte ich die Saiten teilen durch ; Trennzeichen, damit ich:

ADDR1="[email protected]"
ADDR2="[email protected]"

Ich brauche nicht unbedingt die Variablen ADDR1 und ADDR2 . Wenn sie Elemente eines Arrays sind, ist das sogar noch besser.

Nach den Vorschlägen aus den Antworten unten habe ich folgendes gefunden, wonach ich gesucht habe:

#!/usr/bin/env bash

IN="[email protected];[email protected]"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

Ausgabe:

> [[email protected]]
> [[email protected]]

Es gab eine Lösung, die das Setzen von Internal_field_separator (IFS) auf ; . Ich bin mir nicht sicher, was mit dieser Antwort passiert ist. Wie stelle ich IFS auf die Standardeinstellungen zurück?

RE: IFS Lösung, ich habe es versucht und es funktioniert, ich behalte das alte IFS und stelle es dann wieder her:

IN="[email protected];[email protected]"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW, als ich es versuchte

mails2=($IN)

Ich habe nur die erste Saite bekommen, wenn ich sie in einer Schleife drucke, ohne Klammern um $IN es funktioniert.


Abgesehen von den fantastischen Antworten, die bereits zur Verfügung gestellt wurden, wenn es nur darum geht, die Daten auszudrucken, können Sie awk :

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

Dies setzt das Feldtrennzeichen auf ; , so dass es die Felder mit einer for Schleife durchlaufen und entsprechend drucken kann.

Prüfung

$ IN="[email protected];[email protected]"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [[email protected]]
> [[email protected]]

Mit einer anderen Eingabe:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

Das funktioniert auch:

IN="[email protected];[email protected]"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

Sei vorsichtig, diese Lösung ist nicht immer korrekt. Wenn Sie nur "[email protected]" übergeben, wird es ADD1 und ADD2 zugewiesen.


Das hat für mich funktioniert:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

Dies ist der einfachste Weg, dies zu tun.

spo='one;two;three'
OIFS=$IFS
IFS=';'
spo_array=($spo)
IFS=$OIFS
echo ${spo_array[*]}

Ein Einzeiler zum Aufteilen einer Zeichenfolge, getrennt durch ";" in ein Array ist:

IN="[email protected];[email protected]"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

Dadurch wird IFS nur in einer Subshell festgelegt, sodass Sie sich keine Gedanken über das Speichern und Wiederherstellen des Werts machen müssen.


Es gibt ein paar coole Antworten hier (Erator esp.), Aber für etwas, das in anderen Sprachen zu teilen - was ist, was ich die ursprüngliche Frage zu bedeuten - habe ich mich darauf festgelegt:

IN="[email protected];[email protected]"
declare -a a="(${IN/;/ })";

Jetzt sind ${a[0]} , ${a[1]} , usw., wie Sie es erwarten würden. Verwenden Sie ${#a[*]} für die Anzahl der Begriffe. Oder um zu iterieren, natürlich:

for i in ${a[*]}; do echo $i; done

WICHTIGE NOTIZ:

Dies funktioniert in Fällen, in denen es keine Räume gibt, über die man sich Sorgen machen könnte, die mein Problem gelöst haben, aber möglicherweise nicht Ihre Probleme lösen. Gehen Sie in diesem Fall mit den $IFS Lösung (en).


Es gibt einen einfachen und intelligenten Weg:

echo "add:sfff" | xargs -d: -i  echo {}

Aber Sie müssen gnu xargs verwenden, BSD xargs cant support -d delim. Wenn Sie Apple Mac wie mich verwenden. Sie können gnu xargs installieren:

brew install findutils

dann

echo "add:sfff" | gxargs -d: -i  echo {}

Ich denke AWK ist der beste und effizienteste Befehl, um Ihr Problem zu lösen. AWK ist standardmäßig in fast jeder Linux-Distribution in Bash enthalten.

echo "[email protected];[email protected]" | awk -F';' '{print $1,$2}'

wird geben

[email protected] [email protected]

Natürlich können Sie jede E-Mail-Adresse speichern, indem Sie das awk-Druckfeld neu definieren.


Ich habe ein paar Antworten auf den cut , aber sie wurden alle gelöscht. Es ist ein wenig merkwürdig, dass sich niemand darum gekümmert hat, weil ich denke, dass es einer der nützlicheren Befehle für diese Art von Dingen ist, besonders für das Parsen von Log-Dateien mit Trennzeichen.

Im Falle der Aufteilung dieses spezifischen Beispiels in ein Bash-Skript-Array ist tr wahrscheinlich effizienter, aber cut kann verwendet werden und ist effektiver, wenn Sie bestimmte Felder aus der Mitte ziehen möchten.

Beispiel:

$ echo "[email protected];[email protected]" | cut -d ";" -f 1
[email protected]
$ echo "[email protected];[email protected]" | cut -d ";" -f 2
[email protected]

Sie können das natürlich in eine Schleife einfügen und den Parameter -f wiederholen, um jedes Feld unabhängig zu ziehen.

Das wird nützlicher, wenn Sie eine getrennte Protokolldatei mit Zeilen wie folgt haben:

2015-04-27|12345|some action|an attribute|meta data

cut ist sehr praktisch, um diese Datei zu cat und ein bestimmtes Feld zur weiteren Bearbeitung auszuwählen.


In der Android-Shell funktionieren die meisten der vorgeschlagenen Methoden einfach nicht:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

Was ist Arbeit?

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

Wobei // globale Ersetzung bedeutet.


Ohne die IFS zu setzen

Wenn Sie nur einen Doppelpunkt haben, können Sie das tun:

a="foo:bar"
b=${a%:*}
c=${a##*:}

Sie erhalten:

b = foo
c = bar


Wenn es Ihnen nichts ausmacht, sie sofort zu bearbeiten, mache ich das gerne:

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

Sie könnten diese Art von Schleife verwenden, um ein Array zu initialisieren, aber es gibt wahrscheinlich einen einfacheren Weg, dies zu tun. Hoffe das hilft aber.



Wie wäre es mit diesem einen Liner, wenn Sie keine Arrays verwenden:

IFS=';' read ADDR1 ADDR2 <<<$IN

Maybe not the most elegant solution, but works with * and spaces:

IN="[email protected] me.com;*;[email protected]"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

Outputs

> [[email protected] me.com]
> [*]
> [[email protected]]

Other example (delimiters at beginning and end):

IN=";[email protected] me.com;*;[email protected];"
> []
> [[email protected] me.com]
> [*]
> [[email protected]]
> []

Basically it removes every character other than ; making delims eg. ;;; . Then it does for loop from 1 to number-of-delimiters as counted by ${#delims} . The final step is to safely get the $i th part using cut .


Okay guys!

Here's my answer!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

Why this approach is "the best" for me?

Because of two reasons:

  1. Du musst nicht entkommen the delimiter;
  2. Sie werden kein Problem mit Leerzeichen haben . The value will be properly separated in the array!

[] 's



IN='[email protected];[email protected];Charlie Brown <[email protected];!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

Ausgabe:

[email protected]
[email protected]
Charlie Brown <[email protected]
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

Erläuterung: Die einfache Zuweisung mit Klammern () konvertiert die durch Semikola getrennte Liste in ein Array, vorausgesetzt, Sie haben währenddessen IFS korrekt. Die Standard-FOR-Schleife behandelt wie üblich einzelne Elemente in diesem Array. Beachten Sie, dass die Liste, die für die IN-Variable angegeben wird, "hart" sein muss, dh mit einzelnen Ticks.

IFS muss gespeichert und wiederhergestellt werden, da Bash eine Zuweisung nicht wie ein Befehl behandelt. Eine alternative Problemumgehung besteht darin, die Zuordnung innerhalb einer Funktion zu umbrechen und diese Funktion mit einem geänderten IFS aufzurufen. In diesem Fall ist das separate Speichern / Wiederherstellen von IFS nicht erforderlich. Danke für "Bize", dass du darauf hingewiesen hast.





scripting