Hoe splits ik een string in een array in Bash?

In een Bash-script wil ik een regel in stukken splitsen en in een array opslaan.

Bijvoorbeeld, gegeven de regel:

Paris, France, Europe

Ik zou graag willen dat de resulterende array er als volgt uitziet:

array[0] = Paris
array[1] = France
array[2] = Europe

Een eenvoudige implementatie heeft de voorkeur; snelheid maakt niet uit. Hoe kan ik het doen?


Antwoord 1, autoriteit 100%

IFS=', ' read -r -a array <<< "$string"

Houd er rekening mee dat de tekens in $IFSafzonderlijk als scheidingstekens worden behandeld, zodat velden in dit geval kunnen worden gescheiden door ofweleen komma of een spatie in plaats van de volgorde van de twee karakters. Interessant is echter dat er geen lege velden worden gemaakt wanneer komma’s in de invoer verschijnen, omdat de spatie speciaal wordt behandeld.

Om toegang te krijgen tot een afzonderlijk element:

echo "${array[0]}"

Om de elementen te herhalen:

for element in "${array[@]}"
do
    echo "$element"
done

Zowel de index als de waarde ophalen:

for index in "${!array[@]}"
do
    echo "$index ${array[index]}"
done

Het laatste voorbeeld is handig omdat Bash-arrays schaars zijn. Met andere woorden, je kunt een element verwijderen of een element toevoegen en dan zijn de indexen niet aaneengesloten.

unset "array[1]"
array[42]=Earth

Om het aantal elementen in een array te krijgen:

echo "${#array[@]}"

Zoals hierboven vermeld, kunnen arrays schaars zijn, dus u moet de lengte niet gebruiken om het laatste element te krijgen. Zo kun je in Bash 4.2 en later:

echo "${array[-1]}"

in elke versie van Bash (van ergens na 2.05b):

echo "${array[@]: -1:1}"

Grotere negatieve offsets selecteren verder van het einde van de array. Let op de spatie voor het minteken in de oudere vorm. Het is verplicht.


Antwoord 2, autoriteit 37%

Alle antwoorden op deze vraag zijn op de een of andere manier fout.


Onjuist antwoord #1

IFS=', ' read -r -a array <<< "$string"

1:dit is misbruik van $IFS. De waarde van de variabele $IFSwordt nietgenomen als een enkelvoudige variabele-lengtetekenreeksscheidingsteken, maar als een set van één-tekentekenreeksscheidingstekens, waarbij elk veld dat readwordt afgesplitst van de invoerregel kan worden afgesloten met elkteken in de set (komma ofspatie, in dit voorbeeld).

Eigenlijk, voor de echte liefhebbers die er zijn, is de volledige betekenis van $IFSiets meer betrokken. Uit de bash-handleiding:

De shell behandelt elk teken van IFSals een scheidingsteken, en splitst de resultaten van de andere uitbreidingen op in woorden waarbij deze tekens als veldafsluitingen worden gebruikt. Als IFSniet is ingesteld, of als de waarde exact <space><tab><newline>is, de standaard, dan de reeksen van <space> , <tab>en <newline>aan het begin en einde van de resultaten van de vorige uitbreidingen worden genegeerd en elke reeks van >IFS-tekens die niet aan het begin of einde staan, dienen om woorden af te bakenen. Als IFSeen andere waarde heeft dan de standaardwaarde, dan worden reeksen van de witruimtetekens <space>, <tab>en <newline>worden genegeerd aan het begin en einde van het woord, zolang het witruimteteken de waarde van IFSheeft (een IFSwitruimte karakter). Elk teken in IFSdat geen IFSwitruimte is, samen met eventuele aangrenzende IFSwitruimtetekens, begrenst een veld. Een reeks IFSwitruimtetekens wordt ook als scheidingsteken beschouwd. Als de waarde van IFSnull is, vindt er geen woordsplitsing plaats.

Kortom, voor niet-standaard niet-null-waarden van $IFSkunnen velden worden gescheiden door (1) een reeks van een of meer tekens die allemaal uit de set van “IFS-spatie tekens” (dat wil zeggen, welke van de <space>, <tab>en <newline>(“newline” betekent line feed (LF)) overal aanwezig zijn in $IFS), of (2) elk niet-“IFS-spatieteken” dat aanwezig is in $IFSsamen met de “IFS-spatietekens” eromheen in de invoerregel.

Voor de OP is het mogelijk dat de tweede scheidingsmodus die ik in de vorige paragraaf heb beschreven precies is wat hij wil voor zijn invoerreeks, maar we kunnen er vrij zeker van zijn dat de eerste scheidingsmodus die ik beschreef helemaal niet correct is. Wat als zijn invoertekenreeks bijvoorbeeld 'Los Angeles, United States, North America'was?

IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")

2:zelfs als u deze oplossing zou gebruiken met een scheidingsteken van één teken (zoals een komma alleen, dat wil zeggen, zonder volgende spatie of andere bagage), als de waarde van de variabele $stringbevat toevallig LF’s, dan zal readstoppen met verwerken zodra het de eerste LF tegenkomt. De ingebouwde readverwerkt slechts één regel per aanroep. Dit geldt zelfs als u invoer alleenomleidt of omleidt naar de read-instructie, zoals we in dit voorbeeld doen met de here-string-mechanisme, en dus gaat onverwerkte invoer gegarandeerd verloren. De code die de ingebouwde readaandrijft, heeft geen kennis van de gegevensstroom binnen de bevattende opdrachtstructuur.

Je zou kunnen stellen dat dit waarschijnlijk geen probleem zal veroorzaken, maar toch, het is een subtiel gevaar dat indien mogelijk moet worden vermeden. Het wordt veroorzaakt door het feit dat de ingebouwde readeigenlijk twee niveaus van invoer splitst: eerst in regels, dan in velden. Aangezien de OP slechts één niveau van splitsing wil, is dit gebruik van de ingebouwde readniet gepast en moeten we dit vermijden.

3:Een niet voor de hand liggend potentieel probleem met deze oplossing is dat readaltijd het volgveld laat vallen als het leeg is, hoewel het anders lege velden behoudt. Hier is een demo:

string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")

Misschien zou de OP hier niets om geven, maar het is nog steeds een beperking die het waard is om te weten. Het vermindert de robuustheid en algemeenheid van de oplossing.

Dit probleem kan worden opgelost door een dummy volgend scheidingsteken toe te voegen aan de invoerreeks net voordat deze wordt ingevoerd in read, zoals ik later zal aantonen.


Onjuist antwoord #2

string="1:2:3:4:5"
set -f                     # avoid globbing (expansion of *).
array=(${string//:/ })

Soortgelijk idee:

t="one,two,three"
a=($(echo $t | tr ',' "\n"))

(Opmerking: ik heb de ontbrekende haakjes toegevoegd rond de opdrachtvervanging die de antwoorder lijkt te hebben weggelaten.)

Soortgelijk idee:

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)

Deze oplossingen maken gebruik van woordsplitsing in een matrixtoewijzing om de tekenreeks in velden te splitsen. Grappig genoeg, net als read, gebruikt algemene woordsplitsing ook de speciale variabele $IFS, hoewel in dit geval wordt gesuggereerd dat het is ingesteld op de standaardwaarde van <spatie><tab><newline>, en daarom wordt elke reeks van een of meer IFS-tekens (die nu allemaal witruimtetekens zijn) beschouwd als een veldscheidingsteken.

Dit lost het probleem op van twee splitsingsniveaus door read, aangezien woordsplitsing op zich slechts één splitsingsniveau vormt. Maar net als voorheen is het probleem hier dat de afzonderlijke velden in de invoerreeks al $IFS-tekens kunnen bevatten, en dus onjuist zouden worden gesplitst tijdens de woordsplitsing. Dit is toevallig niet het geval voor een van de voorbeeldinvoerreeksen die door deze antwoorders worden geleverd (hoe handig…), maar dat verandert natuurlijk niets aan het feit dat elke codebasis die dit idioom gebruikt, dan het risico loopt opblazen als deze veronderstelling ooit op een bepaald punt in de loop van de tijd werd geschonden. Overweeg nogmaals mijn tegenvoorbeeld van 'Los Angeles, United States, North America'(of 'Los Angeles:United States:North America').

Woorden splitsen wordt normaal gesproken gevolgd door bestandsnaamuitbreiding(ook bekend alspadnaamuitbreiding ook bekend alsglobbing), wat, indien gedaan, mogelijk woorden zou beschadigen die de tekens *, ?, of [gevolgd door ](en, als extglobis ingesteld, fragmenten tussen haakjes die worden voorafgegaan door ?, *, +, @of !) door ze te vergelijken met bestandssysteemobjecten en de woorden (“globs”) dienovereenkomstig uitbreiden. De eerste van deze drie antwoorders heeft dit probleem slim ondermijnd door vooraf set -fuit te voeren om globbing uit te schakelen. Technisch gezien werkt dit (hoewel je waarschijnlijk achteraf set +fmoet toevoegen om globbing weer in te schakelen voor volgende code die ervan kan afhangen), maar het is onwenselijk om te moeten knoeien met globale shell-instellingen om een basis te hacken string-naar-array parseerbewerking in lokale code.

Een ander probleem met dit antwoord is dat alle lege velden verloren gaan. Dit kan al dan niet een probleem zijn, afhankelijk van de toepassing.

Opmerking: als je deze oplossing gaat gebruiken, is het beter om de ${string//:/ }“patroonvervanging” -vorm van parameteruitbreiding, in plaats van de moeite te nemen om een opdrachtvervanging (die de shell afsplitst), een pijplijn opstarten en een extern uitvoerbaar bestand uitvoeren (trof sed), aangezien parameteruitbreiding puur een shell-interne bewerking is. (Ook voor de oplossingen tren sedmoet de invoervariabele dubbel worden geciteerd binnen de opdrachtvervanging; anders zou woordsplitsing van kracht worden in de echocommando en mogelijk knoeien met de veldwaarden. Ook heeft de $(...)vorm van commandovervanging de voorkeur boven het oude `...`formulier omdat het het nesten van opdrachtvervangingen vereenvoudigt en betere syntaxisaccentuering door teksteditors mogelijk maakt.)


Onjuist antwoord #3

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

Dit antwoord is bijna hetzelfde als #2. Het verschil is dat de antwoorder de veronderstelling heeft gemaakt dat de velden worden gescheiden door twee tekens, waarvan er één wordt weergegeven in de standaard $IFS, en de andere niet. Hij heeft dit nogal specifieke geval opgelost door het niet-IFS-vertegenwoordigde teken te verwijderen met behulp van een patroonvervangingsuitbreiding en vervolgens woordsplitsing te gebruiken om de velden op het overgebleven IFS-vertegenwoordigde scheidingsteken te splitsen.

Dit is geen erg algemene oplossing. Verder kan worden beargumenteerd dat de komma hier echt het “primaire” scheidingsteken is, en dat het verwijderen ervan en vervolgens afhankelijk zijn van het spatieteken voor veldsplitsing gewoon verkeerd is. Overweeg nogmaals mijn tegenvoorbeeld: 'Los Angeles, United States, North America'.

Ook nogmaals, bestandsnaamuitbreiding kan de uitgebreide woorden beschadigen, maar dit kan worden voorkomen door globbing tijdelijk uit te schakelen voor de toewijzing met set -fen vervolgens set +f.

Ook, nogmaals, alle lege velden gaan verloren, wat al dan niet een probleem kan zijn, afhankelijk van de toepassing.


Onjuist antwoord #4

string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"

Dit is vergelijkbaar met #2en #3omdat het woordsplitsing gebruikt om de klus te klaren, alleen stelt de code nu expliciet $IFSom alleen het veldscheidingsteken van één teken te bevatten dat aanwezig is in de invoerreeks. Het moet worden herhaald dat dit niet kan werken voor veldscheidingstekens met meerdere tekens, zoals het scheidingsteken voor kommaruimten van het OP. Maar voor een scheidingsteken van één teken zoals de LF die in dit voorbeeld wordt gebruikt, komt het in de buurt van perfect. De velden kunnen niet onbedoeld in het midden worden gesplitst, zoals we hebben gezien bij eerdere foutieve antwoorden, en er is slechts één splitsingsniveau, zoals vereist.

Een probleem is dat bestandsnaamuitbreiding de getroffen woorden corrumpeert zoals eerder beschreven, hoewel dit nogmaals kan worden opgelost door de kritische instructie in set -fen set +f.

Een ander potentieel probleem is dat, aangezien LF kwalificeert als een “IFS-spatieteken” zoals eerder gedefinieerd, alle lege velden verloren gaan, net als in #2en #3. Dit zou natuurlijk geen probleem zijn als het scheidingsteken een niet-“IFS-spatieteken” is, en afhankelijk van de toepassing maakt het misschien niet uit, maar het tast de algemeenheid van de oplossing aan.

Dus, om samen te vatten, ervan uitgaande dat je een scheidingsteken van één teken hebt, en het is ofwel een niet-“IFS-spatieteken” of je geeft niet om lege velden, en je wikkelt de kritieke instructie in set -fen set +f, dan werkt deze oplossing, maar verder niet.

(Ter informatie kan het toewijzen van een LF aan een variabele in bash gemakkelijker worden gedaan met de syntaxis $'...', bijv. IFS=$'\n';.)


Onjuist antwoord #5

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

Soortgelijk idee:

IFS=', ' eval 'array=($string)'

Deze oplossing is in feite een kruising tussen #1(in die zin dat het $IFSinstelt op een komma-spatie) en #2-4(in die zin dat het woordsplitsing gebruikt om de tekenreeks in velden te splitsen). Hierdoor heeft het last van de meeste problemen die te maken hebben met alle bovenstaande foute antwoorden, een beetje zoals de slechtste van alle werelden.

Ook wat betreft de tweede variant, lijkt het misschien alsof de aanroep evalhelemaal niet nodig is, aangezien het argument een letterlijke tekenreeks tussen aanhalingstekens is en daarom statisch bekend is. Maar er is eigenlijk een heel niet voor de hand liggend voordeel aan het op deze manier gebruiken van eval. Normaal gesproken, wanneer u een eenvoudig commando uitvoert dat bestaat uit een variabele toewijzing alleen, wat betekent dat er geen echt commandowoord volgt, wordt de toewijzing van kracht in de shell-omgeving:

IFS=', '; ## changes $IFS in the shell environment

Dit geldt zelfs als het eenvoudige commando meerderevariabele toewijzingen omvat; nogmaals, zolang er geen commandowoord is, hebben alle variabele toewijzingen invloed op de shell-omgeving:

IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment

Maar als de variabeletoewijzing is gekoppeld aan een opdrachtnaam (ik noem dit graag een “prefixtoewijzing”), dan heeft het geeninvloed op de shell-omgeving, en in plaats daarvan alleen op de omgeving van het uitgevoerde commando, ongeacht of het een ingebouwde of externe opdracht is:

IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it

Relevant citaat uit de bash handleiding:

Als er geen opdrachtnaam resulteert, hebben de variabeletoewijzingen invloed op de huidige shell-omgeving. Anders worden de variabelen toegevoegd aan de omgeving van het uitgevoerde commando en hebben ze geen invloed op de huidige shell-omgeving.

Het is mogelijk om deze functie van variabele toewijzing te benutten om $IFSslechts tijdelijk te wijzigen, wat ons in staat stelt het hele save-and-restore-gambit te vermijden zoals dat wordt gedaan met de $OIFSvariabele in de eerste variant. Maar de uitdaging waarmee we hier worden geconfronteerd, is dat het commando dat we moeten uitvoeren zelf slechts een variabele toewijzing is, en daarom zou er geen commandowoord nodig zijn om de $IFS-toewijzing tijdelijk te maken. Je zou bij jezelf kunnen denken, waarom voeg je niet gewoon een no-op commandowoord toe aan de instructie zoals de : builtinom de opdracht $IFStijdelijk te maken? Dit werkt niet omdat het dan ook de opdracht $arraytijdelijk zou maken:

IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command

Dus we zitten in feite in een impasse, een beetje een catch-22. Maar wanneer evalzijn code uitvoert, voert het deze uit in de shell-omgeving, alsof het een normale, statische broncode is, en daarom kunnen we de opdracht $arrayerin uitvoeren het argument evalom het van kracht te laten worden in de shell-omgeving, terwijl de prefixtoewijzing $IFSdie voorafgaat aan het commando evalniet zal overleven de opdracht eval. Dit is precies de truc die wordt gebruikt in de tweede variant van deze oplossing:

IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does

Dus, zoals je kunt zien, is het eigenlijk best een slimme truc, en volbrengt het precies wat nodig is (tenminste met betrekking tot het uitvoeren van opdrachten) op een nogal niet voor de hand liggende manier. Ik ben eigenlijk niet tegen deze truc in het algemeen, ondanks de betrokkenheid van eval; pas op dat u de argumentreeks één keer citeert om u te beschermen tegen beveiligingsrisico’s.

Maar nogmaals, vanwege de “slechtste van alle werelden” agglomeratie van problemen, is dit nog steeds een verkeerd antwoord op de eis van het OP.


Onjuist antwoord #6

IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)

Eh… wat? De OP heeft een stringvariabele die in een array moet worden geparseerd. Dit “antwoord” begint met de woordelijke inhoud van de invoerreeks die in een letterlijke array is geplakt. Ik denk dat dat een manier is om het te doen.

Het lijkt erop dat de beantwoorder ervan uit is gegaan dat de variabele $IFSalle bash-parsing in alle contexten beïnvloedt, wat niet waar is. Uit de bash-handleiding:

IFS    De interne veldscheider die wordt gebruikt voor het splitsen van woorden na uitbreiding en om regels in woorden te splitsen met het ingebouwde commando lees. De standaardwaarde is <space><tab><newline>.

Dus de speciale variabele $IFSwordt eigenlijk alleen gebruikt in twee contexten: (1) woordsplitsing die wordt uitgevoerd na uitbreiding(wat betekent nietbij het ontleden van bash-broncode) en (2) voor het splitsen van invoerregels in woorden door de ingebouwde read.

Laat me proberen dit duidelijker te maken. Ik denk dat het goed is om een onderscheid te maken tussen parserenen uitvoering. Bash moet eerst de broncode parseren, wat duidelijk een parsing-gebeurtenis is, en later voert hetde code uit, wat het moment is waarop uitbreiding binnenkomt De foto. Uitbreiding is echt een uitvoeringevenement. Verder ben ik het oneens met de beschrijving van de variabele $IFSdie ik hierboven heb geciteerd; in plaats van te zeggen dat woordsplitsing na expansiewordt uitgevoerd, zou ik zeggen dat woordsplitsing tijdenswordt uitgevoerd, of, misschien nog preciezer, woordsplitsing is een onderdeel vanhet uitbreidingsproces. De zinsnede “woord splitsen” verwijst alleen naar deze stap van uitbreiding; het mag nooit worden gebruikt om te verwijzen naar het ontleden van bash-broncode, hoewel de documenten helaas vaak de woorden “split” en “woorden” lijken te gebruiken. Hier is een relevant fragment uit de linux.die.net-versievan de bash-handleiding:

Uitbreiding wordt uitgevoerd op de opdrachtregel nadat deze in woorden is opgesplitst. Er zijn zeven soorten uitbreidingen: accolade-uitbreiding, tilde-uitbreiding, parameter- en variabele uitbreiding, opdrachtvervanging, rekenkundige uitbreiding, woordsplitsingen uitbreiding padnaam.

De volgorde van uitbreidingen is: accoladeuitbreiding; tilde-uitbreiding, parameter- en variabele-uitbreiding, rekenkundige uitbreiding en opdrachtvervanging (van links naar rechts uitgevoerd); woord splitsen; en padnaamuitbreiding.

Je zou kunnen pleiten voor de GNU-versievan de handleiding doet het iets beter, omdat er wordt gekozen voor het woord “tokens” in plaats van “woorden” in de eerste zin van het gedeelte Uitbreiding:

Uitbreiding wordt uitgevoerd op de opdrachtregel nadat deze is opgesplitst in tokens.

Het belangrijke punt is, $IFSverandert niets aan de manier waarop bash de broncode parseert. Het ontleden van bash-broncode is in feite een zeer complex proces waarbij de verschillende elementen van shell-grammatica worden herkend, zoals opdrachtreeksen, opdrachtlijsten, pijplijnen, parameteruitbreidingen, rekenkundige vervangingen en opdrachtvervangingen. Voor het grootste deel kan het bash-parsingproces niet worden gewijzigd door acties op gebruikersniveau zoals variabele toewijzingen (er zijn enkele kleine uitzonderingen op deze regel; zie bijvoorbeeld de verschillende compatxxshell-instellingen, die bepaalde aspecten van on-the-fly parseergedrag). De stroomopwaartse “woorden”/”tokens” die het resultaat zijn van dit complexe ontledingsproces worden vervolgens uitgebreid volgens het algemene proces van “uitbreiding” zoals opgesplitst in de bovenstaande documentatiefragmenten, waarbij woordsplitsing van de uitgebreide (uitbreidende?) tekst in stroomafwaartse woorden is slechts één stap van dat proces. Woordsplitsing raakt alleen tekst die uit een voorgaande uitbreidingsstap is uitgespuugd; het heeft geen invloed op letterlijke tekst die direct uit de bytestream van de bron is geparseerd.


Onjuist antwoord #7

string='first line
        second line
        third line'
while read -r line; do lines+=("$line"); done <<<"$string"

Dit is een van de beste oplossingen. Merk op dat we weer readgebruiken. Heb ik niet eerder gezegd dat readongepast is omdat het twee splitsingsniveaus uitvoert, terwijl we er maar één nodig hebben? De truc hier is dat je readop zo’n manier kunt aanroepen dat het effectief slechts één niveau van splitsing doet, namelijk door slechts één veld per aanroep af te splitsen, wat de kosten met zich meebrengt om het herhaaldelijk te moeten aanroepen in een lus. Het is een beetje handigheid, maar het werkt.

Maar er zijn problemen. Ten eerste: wanneer u ten minste één NAAM-argument opgeeft voor read, negeert het automatisch voorloop- en volgspaties in elk veld dat is afgesplitst van de invoerreeks. Dit gebeurt ongeacht of $IFSis ingesteld op de standaardwaarde of niet, zoals eerder in dit bericht is beschreven. Nu kan het OP dit niet schelen voor zijn specifieke use-case, en in feite kan het een wenselijk kenmerk zijn van het parseergedrag. Maar niet iedereen die een string in velden wil ontleden, zal dit willen. Er is echter een oplossing: een enigszins niet voor de hand liggend gebruik van readis om nul NAME-argumenten door te geven. In dit geval slaat readde volledige invoerregel op die het van de invoerstroom krijgt in een variabele met de naam $REPLY, en als bonus doet het nietvoorloop- en volgspaties van de waarde verwijderen. Dit is een zeer robuust gebruik van readwaarvan ik in mijn shell-programmeercarrière vaak gebruik heb gemaakt. Hier is een demonstratie van het verschil in gedrag:

string=$'  a  b  \n  c  d  \n  e  f  '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a  b" [1]="c  d" [2]="e  f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="  a  b  " [1]="  c  d  " [2]="  e  f  ") ## no trimming

Het tweede probleem met deze oplossing is dat het niet het geval is van een aangepast veldscheidingsteken, zoals de kommaruimte van het OP. Zoals eerder worden scheidingstekens voor meerdere tekens niet ondersteund, wat een ongelukkige beperking van deze oplossing is. We zouden kunnen proberen om op zijn minst te splitsen op komma’s door het scheidingsteken op te geven voor de optie -d, maar kijk wat er gebeurt:

string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")

Het was te verwachten dat de niet-verantwoorde omringende witruimte in de veldwaarden werd getrokken, en daarom zou dit later gecorrigeerd moeten worden door middel van trimbewerkingen (dit zou ook rechtstreeks in de while-lus kunnen worden gedaan). Maar er is nog een duidelijke fout: Europa ontbreekt! Wat is er mee gebeurd? Het antwoord is dat readeen falende retourcode retourneert als deze het einde van het bestand raakt (in dit geval kunnen we het einde van de string noemen) zonder een laatste veldterminator op het laatste veld tegen te komen. Hierdoor breekt de while-loop voortijdig af en verliezen we het laatste veld.

Technisch gezien had dezelfde fout ook de vorige voorbeelden; het verschil is dat het veldscheidingsteken is genomen als LF, wat de standaard is wanneer u de optie -dniet opgeeft, en de <<<(“here-string”) mechanisme voegt automatisch een LF toe aan de string net voordat het deze invoert als invoer voor het commando. Daarom hebben we in die gevallen per ongelukhet probleem van een weggelaten laatste veld opgelost door onbewust een extra dummy-terminator aan de invoer toe te voegen. Laten we deze oplossing de “dummy-terminator”-oplossing noemen. We kunnen de dummy-terminator-oplossing handmatig toepassen voor elk aangepast scheidingsteken door het zelf samen te voegen met de invoertekenreeks wanneer we deze in de hier-tekenreeks instantiëren:

a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

Zo, probleem opgelost. Een andere oplossing is om de while-loop alleen te verbreken als zowel (1) readeen fout heeft geretourneerd als (2) $REPLYleeg is, wat betekent readkon geen karakters lezen voordat het einde van het bestand bereikte. Demo:

a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

Deze benadering onthult ook de geheime LF die automatisch wordt toegevoegd aan de here-string door de <<<omleidingsoperator. Het kan natuurlijk afzonderlijk worden verwijderd door een expliciete trimbewerking zoals zojuist beschreven, maar de handmatige dummy-terminator-aanpak lost het natuurlijk direct op, dus daar kunnen we gewoon mee doorgaan. De handmatige dummy-terminator-oplossing is eigenlijk best handig omdat het beide problemen (het drop-final-field-probleem en het bijgevoegde-LF-probleem) in één keer oplost.

Dus over het algemeen is dit een behoorlijk krachtige oplossing. Het enige resterende zwaktepunt is een gebrek aan ondersteuning voor scheidingstekens voor meerdere tekens, waar ik later op in zal gaan.


Onjuist antwoord #8

string='first line
        second line
        third line'
readarray -t lines <<<"$string"

(Dit komt eigenlijk uit hetzelfde bericht als #7; de antwoorder gaf twee oplossingen in hetzelfde bericht.)

De ingebouwde readarray, wat een synoniem is voor mapfile, is ideaal. Het is een ingebouwd commando dat een bytestream in één keer in een arrayvariabele parseert; geen gedoe met loops, conditionals, vervangingen of iets anders. En het verwijdert niet heimelijk witruimte van de invoerreeks. En (als -Oniet wordt gegeven) wordt de doelarray gemakkelijk gewist voordat deze eraan wordt toegewezen. Maar het is nog steeds niet perfect, vandaar mijn kritiek erop als een “verkeerd antwoord”.

Ten eerste, om dit uit de weg te ruimen, merk op dat, net als het gedrag van readbij het ontleden van velden, readarrayhet achterliggende veld laat vallen als het is leeg. Nogmaals, dit is waarschijnlijk geen zorg voor de OP, maar het kan voor sommige use-cases zijn. Ik kom hier zo op terug.

Ten tweede ondersteunt het, net als voorheen, geen scheidingstekens voor meerdere tekens. Ik zal hier ook zo dadelijk een oplossing voor geven.

Ten derde, de geschreven oplossing ontleedt de invoerreeks van het OP niet, en kan in feite niet worden gebruikt zoals het is om het te ontleden. Ik zal hier ook even op ingaan.

Om bovenstaande redenen beschouw ik dit nog steeds als een “verkeerd antwoord” op de vraag van de OP. Hieronder geef ik wat ik beschouw als het juiste antwoord.


Juist antwoord

Hier is een naïeve poging om #8te laten werken door gewoon de optie -dop te geven:

string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')

We zien dat het resultaat identiek is aan het resultaat dat we kregen van de dubbel-voorwaardelijke benadering van de looping read-oplossing besproken in #7. We kunnen dit bijnaoplossen met de handmatige dummy-terminator-truc:

readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')

Het probleem hier is dat readarrayhet volgveld bewaarde, aangezien de <<<omleidingsoperator de LF aan de invoertekenreeks toevoegde, en daarom veld was nietleeg (anders zou het zijn weggelaten). We kunnen hiervoor zorgen door het laatste array-element achteraf expliciet uit te schakelen:

readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")

De enige twee problemen die overblijven, die daadwerkelijk verband houden, zijn (1) de overbodige witruimte die moet worden bijgesneden, en (2) het gebrek aan ondersteuning voor scheidingstekens van meerdere tekens.

De witruimte kan natuurlijk achteraf worden bijgesneden (zie bijvoorbeeld Hoe kan ik witruimte uit een Bash-variabele bijsnijden?). Maar als we een scheidingsteken van meerdere tekens kunnen hacken, dan zou dat beide problemen in één keer oplossen.

Helaas is er geen directemanier om een scheidingsteken voor meerdere tekens te laten werken. De beste oplossing die ik heb bedacht, is om de invoerreeks voor te verwerken om het scheidingsteken van meerdere tekens te vervangen door een scheidingsteken van één teken dat gegarandeerd niet botst met de inhoud van de invoerreeks. Het enige teken dat deze garantie heeft, is de NUL-byte. Dit komt omdat variabelen in bash (hoewel overigens niet in zsh) de NUL-byte niet kunnen bevatten. Deze voorbewerkingsstap kan inline worden uitgevoerd in een procesvervanging. Ga als volgt te werk met awk:

readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

Daar, eindelijk! Deze oplossing zal velden niet per ongeluk in het midden splitsen, zal niet voortijdig worden weggesneden, zal geen lege velden laten vallen, zal zichzelf niet beschadigen bij bestandsnaamuitbreidingen, zal niet automatisch voor- en achterliggende witruimte verwijderen, zal geen verstekeling LF achterlaten aan het einde, vereist geen lussen en neemt geen genoegen met een scheidingsteken van één teken.


Trimoplossing

Ten slotte wilde ik mijn eigen redelijk ingewikkelde trimoplossing demonstreren met behulp van de obscure -C callback-optie van readarray. Helaas heb ik geen ruimte meer tegen de draconische postlimiet van 30.000 tekens van Stack Overflow, dus ik kan het niet uitleggen. Ik laat dat als een oefening voor de lezer.

function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")

Antwoord 3, autoriteit 19%

Hier is een manier zonder IFS in te stellen:

string="1:2:3:4:5"
set -f                      # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
    echo "$i=>${array[i]}"
done

Het idee is om stringvervanging te gebruiken:

${string//substring/replacement}

om alle overeenkomsten van $substring te vervangen door witruimte en vervolgens de vervangende string te gebruiken om een array te initialiseren:

(element1 element2 ... elementN)

Opmerking: dit antwoord maakt gebruik van de split+glob-operator. Om uitbreiding van sommige tekens (zoals *) te voorkomen, is het dus een goed idee om het globbing voor dit script te pauzeren.


Antwoord 4, autoriteit 9%

t="one,two,three"
a=($(echo "$t" | tr ',' '\n'))
echo "${a[2]}"

Drukt drie af


Antwoord 5, autoriteit 3%

Het geaccepteerde antwoord werkt voor waarden op één regel.
Als de variabele meerdere regels heeft:

string='first line
        second line
        third line'

We hebben een heel ander commando nodig om alle regels te krijgen:

while read -r line; do lines+=("$line"); done <<<"$string"

Of de veel eenvoudigere bash readarray:

readarray -t lines <<<"$string"

Alle regels afdrukken is heel eenvoudig door gebruik te maken van een printf-functie:

printf ">[%s]\n" "${lines[@]}"
>[first line]
>[        second line]
>[        third line]

Antwoord 6, autoriteit 2%

Soms gebeurde het me dat de methode beschreven in het geaccepteerde antwoord niet werkte, vooral als het scheidingsteken een regelterugloop is.
In die gevallen heb ik het op deze manier opgelost:

string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
for line in "${lines[@]}"
    do
        echo "--> $line"
done

Antwoord 7

als u macOS gebruikt en readarray niet kunt gebruiken, kunt u dit eenvoudig doen-

MY_STRING="string1 string2 string3"
array=($MY_STRING)

Om de elementen te herhalen:

for element in "${array[@]}"
do
    echo $element
done

Antwoord 8

Dit werkt voor mij op OSX:

string="1 2 3 4 5"
declare -a array=($string)

Als uw tekenreeks een ander scheidingsteken heeft, vervangt u deze eerst door spatie:

string="1,2,3,4,5"
delimiter=","
declare -a array=($(echo $string | tr "$delimiter" " "))

Eenvoudig 🙂


Antwoord 9

Dit is vergelijkbaar met de aanpak van Jmoney38, maar met sed:

string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
echo ${array[0]}

Afdrukken 1


Antwoord 10

De sleutel tot het splitsen van je string in een array is het scheidingsteken van meerdere tekens van ", ". Elke oplossing die IFSgebruikt voor scheidingstekens voor meerdere tekens is inherent verkeerd, aangezien IFS een set van die tekens is, geen string.

Als u IFS=", "toewijst, breekt de tekenreeks op OFWEL ","OF " "of een combinatie daarvan wat geen nauwkeurige weergave is van het scheidingsteken van twee tekens van ", ".

U kunt awkof sedgebruiken om de tekenreeks te splitsen, met procesvervanging:

#!/bin/bash
str="Paris, France, Europe"
array=()
while read -r -d $'\0' each; do   # use a NUL terminated field separator 
    array+=("$each")
done < <(printf "%s" "$str" | awk '{ gsub(/,[ ]+|$/,"\0"); print }')
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output

Het is efficiënter om een regex rechtstreeks in Bash te gebruiken:

#!/bin/bash
str="Paris, France, Europe"
array=()
while [[ $str =~ ([^,]+)(,[ ]+|$) ]]; do
    array+=("${BASH_REMATCH[1]}")   # capture the field
    i=${#BASH_REMATCH}              # length of field + delimiter
    str=${str:i}                    # advance the string by that length
done                                # the loop deletes $str, so make a copy if needed
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output...

Met de tweede vorm is er geen sub-shell en zal deze inherent sneller zijn.


Bewerkt door bgoldst:Hier zijn enkele benchmarks die mijn readarray-oplossing vergelijken met de regex-oplossing van dawg, en ik heb ook de read-oplossing voor de verdomd (opmerking: ik heb de regex-oplossing enigszins aangepast voor meer harmonie met mijn oplossing) (zie ook mijn opmerkingen onder het bericht):

## competitors
function c_readarray { readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); unset 'a[-1]'; };
function c_read { a=(); local REPLY=''; while read -r -d ''; do a+=("$REPLY"); done < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); };
function c_regex { a=(); local s="$1, "; while [[ $s =~ ([^,]+),\  ]]; do a+=("${BASH_REMATCH[1]}"); s=${s:${#BASH_REMATCH}}; done; };
## helper functions
function rep {
    local -i i=-1;
    for ((i = 0; i<$1; ++i)); do
        printf %s "$2";
    done;
}; ## end rep()
function testAll {
    local funcs=();
    local args=();
    local func='';
    local -i rc=-1;
    while [[ "$1" != ':' ]]; do
        func="$1";
        if [[ ! "$func" =~ ^[_a-zA-Z][_a-zA-Z0-9]*$ ]]; then
            echo "bad function name: $func" >&2;
            return 2;
        fi;
        funcs+=("$func");
        shift;
    done;
    shift;
    args=("$@");
    for func in "${funcs[@]}"; do
        echo -n "$func ";
        { time $func "${args[@]}" >/dev/null 2>&1; } 2>&1| tr '\n' '/';
        rc=${PIPESTATUS[0]}; if [[ $rc -ne 0 ]]; then echo "[$rc]"; else echo; fi;
    done| column -ts/;
}; ## end testAll()
function makeStringToSplit {
    local -i n=$1; ## number of fields
    if [[ $n -lt 0 ]]; then echo "bad field count: $n" >&2; return 2; fi;
    if [[ $n -eq 0 ]]; then
        echo;
    elif [[ $n -eq 1 ]]; then
        echo 'first field';
    elif [[ "$n" -eq 2 ]]; then
        echo 'first field, last field';
    else
        echo "first field, $(rep $[$1-2] 'mid field, ')last field";
    fi;
}; ## end makeStringToSplit()
function testAll_splitIntoArray {
    local -i n=$1; ## number of fields in input string
    local s='';
    echo "===== $n field$(if [[ $n -ne 1 ]]; then echo 's'; fi;) =====";
    s="$(makeStringToSplit "$n")";
    testAll c_readarray c_read c_regex : "$s";
}; ## end testAll_splitIntoArray()
## results
testAll_splitIntoArray 1;
## ===== 1 field =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.000s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 10;
## ===== 10 fields =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.001s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 100;
## ===== 100 fields =====
## c_readarray   real  0m0.069s   user 0m0.000s   sys  0m0.062s
## c_read        real  0m0.065s   user 0m0.000s   sys  0m0.046s
## c_regex       real  0m0.005s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 1000;
## ===== 1000 fields =====
## c_readarray   real  0m0.084s   user 0m0.031s   sys  0m0.077s
## c_read        real  0m0.092s   user 0m0.031s   sys  0m0.046s
## c_regex       real  0m0.125s   user 0m0.125s   sys  0m0.000s
##
testAll_splitIntoArray 10000;
## ===== 10000 fields =====
## c_readarray   real  0m0.209s   user 0m0.093s   sys  0m0.108s
## c_read        real  0m0.333s   user 0m0.234s   sys  0m0.109s
## c_regex       real  0m9.095s   user 0m9.078s   sys  0m0.000s
##
testAll_splitIntoArray 100000;
## ===== 100000 fields =====
## c_readarray   real  0m1.460s   user 0m0.326s   sys  0m1.124s
## c_read        real  0m2.780s   user 0m1.686s   sys  0m1.092s
## c_regex       real  17m38.208s   user 15m16.359s   sys  2m19.375s
##

Antwoord 11

Pure bash oplossing voor scheidingstekens met meerdere tekens.

Zoals anderen in deze thread hebben opgemerkt, gaf de vraag van de OP een voorbeeld van een door komma’s gescheiden tekenreeks die in een array moest worden geparseerd, maar gaf niet aan of hij/zij alleen geïnteresseerd was in kommascheidingstekens, scheidingstekens met één teken of scheidingstekens met meerdere tekens.

Omdat Google dit antwoord meestal bovenaan of dicht bij de zoekresultaten plaatst, wilde ik de lezers een krachtig antwoord geven op de vraag van scheidingstekens voor meerdere tekens, aangezien dat ook in ten minste één antwoord wordt genoemd.

Als u op zoek bent naar een oplossing voor een probleem met scheidingstekens van meerdere tekens, raad ik u aan Mallikarjun M‘s post, in het bijzonder de reactie van gniourf_gniourf
die deze elegante pure BASH-oplossing levert met behulp van parameteruitbreiding:

#!/bin/bash
str="LearnABCtoABCSplitABCaABCString"
delimiter=ABC
s=$str$delimiter
array=();
while [[ $s ]]; do
    array+=( "${s%%"$delimiter"*}" );
    s=${s#*"$delimiter"};
done;
declare -p array

Link naar geciteerde opmerking/ bericht waarnaar wordt verwezen

Link naar geciteerde vraag: Hoe splitsen een tekenreeks op een scheidingsteken van meerdere tekens in bash?


Antwoord 12

Een andere manier om het te doen zonder IFS te wijzigen:

read -r -a myarray <<< "${string//, /$IFS}"

In plaats van IFS te wijzigen zodat het overeenkomt met ons gewenste scheidingsteken, kunnen we alle exemplaren van ons gewenste scheidingsteken ", "vervangen door de inhoud van $IFSvia "${string//, /$IFS}".

Misschien gaat dit langzaam voor zeer grote strings?

Dit is gebaseerd op het antwoord van Dennis Williamson.


Antwoord 13

We kunnen de opdracht tr gebruiken om de tekenreeks in het array-object te splitsen. Het werkt zowel MacOS als Linux

 #!/usr/bin/env bash
  currentVersion="1.0.0.140"
  arrayData=($(echo $currentVersion | tr "." "\n"))
  len=${#arrayData[@]}
  for (( i=0; i<=$((len-1)); i++ )); do 
       echo "index $i - value ${arrayData[$i]}"
  done

Een andere optie, gebruik het IFS-commando

IFS='.' read -ra arrayData <<< "$currentVersion"
#It is the same as tr
arrayData=($(echo $currentVersion | tr "." "\n"))
#Print the split string
for i in "${arrayData[@]}"
do
    echo $i
done

Antwoord 14

Probeer dit

IFS=', '; array=(Paris, France, Europe)
for item in ${array[@]}; do echo $item; done

Het is eenvoudig. Als je wilt, kun je ook een verklaring toevoegen (en ook de komma’s verwijderen):

IFS=' ';declare -a array=(Paris France Europe)

De IFS is toegevoegd om het bovenstaande ongedaan te maken, maar het werkt zonder deze in een nieuwe bash-instantie


Antwoord 15

Ik kwam dit bericht tegen toen ik een invoer wilde ontleden zoals:
woord1,woord2,…

Geen van bovenstaande heeft me geholpen. heb het opgelost door awk te gebruiken. Als het iemand helpt:

STRING="value1,value2,value3"
array=`echo $STRING | awk -F ',' '{ s = $1; for (i = 2; i <= NF; i++) s = s "\n"$i; print s; }'`
for word in ${array}
do
        echo "This is the word $word"
done

Antwoord 16

UPDATE: doe dit niet vanwege problemen met eval.

Met iets minder ceremonie:

IFS=', ' eval 'array=($string)'

bijv.

string="foo, bar,baz"
IFS=', ' eval 'array=($string)'
echo ${array[1]} # -> bar

Antwoord 17

Hier is mijn hack!

Snaren voor strings splitsen is vrij saai om te doen met bash. Wat er gebeurt, is dat we beperkte benaderingen hebben die slechts in een paar gevallen werken (gesplitst door “;”, “/”, “.” enzovoort) of we hebben een verscheidenheid aan bijwerkingen in de output.

Voor de onderstaande aanpak zijn een aantal manoeuvres nodig geweest, maar ik denk dat deze voor de meeste van onze behoeften zal werken!

#!/bin/bash
# --------------------------------------
# SPLIT FUNCTION
# ----------------
F_SPLIT_R=()
f_split() {
    : 'It does a "split" into a given string and returns an array.
    Args:
        TARGET_P (str): Target string to "split".
        DELIMITER_P (Optional[str]): Delimiter used to "split". If not 
    informed the split will be done by spaces.
    Returns:
        F_SPLIT_R (array): Array with the provided string separated by the 
    informed delimiter.
    '
    F_SPLIT_R=()
    TARGET_P=$1
    DELIMITER_P=$2
    if [ -z "$DELIMITER_P" ] ; then
        DELIMITER_P=" "
    fi
    REMOVE_N=1
    if [ "$DELIMITER_P" == "\n" ] ; then
        REMOVE_N=0
    fi
    # NOTE: This was the only parameter that has been a problem so far! 
    # By Questor
    # [Ref.: https://unix.stackexchange.com/a/390732/61742]
    if [ "$DELIMITER_P" == "./" ] ; then
        DELIMITER_P="[.]/"
    fi
    if [ ${REMOVE_N} -eq 1 ] ; then
        # NOTE: Due to bash limitations we have some problems getting the 
        # output of a split by awk inside an array and so we need to use 
        # "line break" (\n) to succeed. Seen this, we remove the line breaks 
        # momentarily afterwards we reintegrate them. The problem is that if 
        # there is a line break in the "string" informed, this line break will 
        # be lost, that is, it is erroneously removed in the output! 
        # By Questor
        TARGET_P=$(awk 'BEGIN {RS="dn"} {gsub("\n", "3F2C417D448C46918289218B7337FCAF"); printf $0}' <<< "${TARGET_P}")
    fi
    # NOTE: The replace of "\n" by "3F2C417D448C46918289218B7337FCAF" results 
    # in more occurrences of "3F2C417D448C46918289218B7337FCAF" than the 
    # amount of "\n" that there was originally in the string (one more 
    # occurrence at the end of the string)! We can not explain the reason for 
    # this side effect. The line below corrects this problem! By Questor
    TARGET_P=${TARGET_P%????????????????????????????????}
    SPLIT_NOW=$(awk -F"$DELIMITER_P" '{for(i=1; i<=NF; i++){printf "%s\n", $i}}' <<< "${TARGET_P}")
    while IFS= read -r LINE_NOW ; do
        if [ ${REMOVE_N} -eq 1 ] ; then
            # NOTE: We use "'" to prevent blank lines with no other characters 
            # in the sequence being erroneously removed! We do not know the 
            # reason for this side effect! By Questor
            LN_NOW_WITH_N=$(awk 'BEGIN {RS="dn"} {gsub("3F2C417D448C46918289218B7337FCAF", "\n"); printf $0}' <<< "'${LINE_NOW}'")
            # NOTE: We use the commands below to revert the intervention made 
            # immediately above! By Questor
            LN_NOW_WITH_N=${LN_NOW_WITH_N%?}
            LN_NOW_WITH_N=${LN_NOW_WITH_N#?}
            F_SPLIT_R+=("$LN_NOW_WITH_N")
        else
            F_SPLIT_R+=("$LINE_NOW")
        fi
    done <<< "$SPLIT_NOW"
}
# --------------------------------------
# HOW TO USE
# ----------------
STRING_TO_SPLIT="
 * How do I list all databases and tables using psql?
\"
sudo -u postgres /usr/pgsql-9.4/bin/psql -c \"\l\"
sudo -u postgres /usr/pgsql-9.4/bin/psql <DB_NAME> -c \"\dt\"
\"
\"
\list or \l: list all databases
\dt: list all tables in the current database
\"
[Ref.: https://dba.stackexchange.com/questions/1285/how-do-i-list-all-databases-and-tables-using-psql]
"
f_split "$STRING_TO_SPLIT" "bin/psql -c"
# --------------------------------------
# OUTPUT AND TEST
# ----------------
ARR_LENGTH=${#F_SPLIT_R[*]}
for (( i=0; i<=$(( $ARR_LENGTH -1 )); i++ )) ; do
    echo " > -----------------------------------------"
    echo "${F_SPLIT_R[$i]}"
    echo " < -----------------------------------------"
done
if [ "$STRING_TO_SPLIT" == "${F_SPLIT_R[0]}bin/psql -c${F_SPLIT_R[1]}" ] ; then
    echo " > -----------------------------------------"
    echo "The strings are the same!"
    echo " < -----------------------------------------"
fi

Antwoord 18

Omdat er zoveel manieren zijn om dit op te lossen, laten we beginnen met te definiëren wat we in onze oplossing willen zien.

  1. Bash biedt hiervoor een ingebouwde readarray. Laten we het gebruiken.
  2. Vermijd lelijke en onnodige trucs zoals het veranderen van IFS, looping, het gebruik van evalof het toevoegen van een extra element en het vervolgens verwijderen ervan.
  3. Zoek een eenvoudige, leesbare aanpak die gemakkelijk kan worden aangepast aan soortgelijke problemen.

De opdracht readarrayis het gemakkelijkst te gebruiken met nieuwe regels als scheidingsteken. Met andere scheidingstekens kan het een extra element aan de array toevoegen. De schoonste benadering is om eerst onze invoer aan te passen in een vorm die goed werkt met readarrayvoordat we deze doorgeven.

De invoer in dit voorbeeld heeft geeneen scheidingsteken voor meerdere tekens. Als we een beetje gezond verstand toepassen, wordt dit het best begrepen als door komma’s gescheiden invoer waarvoor elk element mogelijk moet worden bijgesneden. Mijn oplossing is om de invoer door komma’s in meerdere regels te splitsen, elk element bij te snijden en alles door te geven aan readarray.

string='  Paris,France  ,   All of Europe  '
readarray -t foo < <(tr ',' '\n' <<< "$string" |sed 's/^ *//' |sed 's/ *$//')
declare -p foo
# declare -a foo='([0]="Paris" [1]="France" [2]="All of Europe")'

Antwoord 19

Voor elementen met meerdere lijnen, waarom niet zoiets als

$ array=($(echo -e $'a a\nb b' | tr ' ' '§')) && array=("${array[@]//§/ }") && echo "${array[@]/%/ INTERELEMENT}"
a a INTERELEMENT b b INTERELEMENT

Antwoord 20

Een andere manier zou zijn:

string="Paris, France, Europe"
IFS=', ' arr=(${string})

Nu zijn je elementen opgeslagen in de “arr”-array.
De elementen doorlopen:

for i in ${arr[@]}; do echo $i; done

Antwoord 21

Een andere benadering kan zijn:

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

Hierna is ‘arr’ een array met vier strings.
Dit vereist geen IFS of lezen of andere speciale dingen, dus veel eenvoudiger en directer.

Other episodes