bash: IFS "stuck" after temporarily changing it for array building
Asked Answered
H

1

0

I've encountered a strange problem after temporarily changing IFS for the purpose of array building:

$ echo "1 2 3" |while read myVar1 myVar2; do echo "myVar1: ${myVar1}"; echo "myVar2: ${myVar2}"; done
myVar1: 1
myVar2: 2 3
$ IFS=':' myPaths=( ${PATH} )  # this works: I have /home/morgwai/bin on ${myPaths[0]} , /usr/local/sbin on ${myPaths[1]} and so on
$ echo "1 2 3" |while read myVar1 myVar2; do echo "myVar1: ${myVar1}"; echo "myVar2: ${myVar2}"; done
myVar1: 1 2 3
myVar2: 
$ echo $IFS

$ echo "1:2:3" |while read myVar1 myVar2; do echo "myVar1: ${myVar1}"; echo "myVar2: ${myVar2}"; done ;
myVar1: 1
myVar2: 2:3

Normally when I change IFS temporarily for any other command than array building (for example IFS=',' echo whatever) its value is changed only during the execution of that, however here it seems as if IFS got permanently changed to a colon (although echo $IFS doesn't show this, which is even more strange...).

Is this a bug or somehow an expected behavior that I don't understand?
I'm using bash version 4.4.18 if it matters...

Note: I know that I can build the same array using IFS=':' read -a myPaths <<< ${PATH} and then IFS gets reverted to the default value normally, but that's not the point: I'm trying to understand what actually happens in the example I gave above.

Thanks!

Histone answered 11/7, 2020 at 23:50 Comment(12)
You're just setting variables, not setting a variable followed by executing a command.Solicitous
@Solicitous this would indeed explain why the change seems permanent, but why echo $IFS shows blank line instead of a colon?Histone
BTW, I'd suggest printf '%q\n' "$IFS" to display the current value in a visually unambiguous form.Countless
...but yes, in the case of IFS=':' myPaths=( ${PATH} ), it's just two assignments, and neither of them is temporary; there has to be an actual regular command on the line for the assignments preceding them to be treated as environment variables scoped to that command.Countless
BTW, array=( $content ) is not a good practice -- unless you disable globbing, it has potential to to do more things than just split on IFS. See BashPitfalls #50.Countless
@CharlesDuffy the way you edited my question makes it substantially different: I do not set IFS to coma in the beginning as you do in your edit and echo $IFS prints blank line, which is a important factor and something I'm hoping to get explanation for alsoHistone
@Solicitous I found the explanation for empty line on echo $IFS, it's quite funny ;) ubuntuforums.org/showthread.php?t=1454313Histone
Btw, modern bash lets you use ${IFS@E} or ${IFS@Q} instead of that printf bit.Solicitous
Gotcha. I tried to edit to clarify, but evidently didn't follow what you were trying to ask. (Which does line up with the idea that clarification was needed).Countless
(re: setting to a comma in the beginning, that was to have it at a known value, so we could compare against that known value later; in the original question, it was undefined what the value of IFS was expected to be).Countless
Here is a good article on the topic: IFSCurse
@CharlesDuffy yeah, in the end it turned out to be 2 separate issues, but initially I thought they were tightly related, hence the confusion: sorry for this. Anyway, thanks for your input and willingness to help :)Histone
S
4

You're just setting variables, not setting a variable followed by executing a command (ie, the way you build array is a pure variable assignment, not a command, hence both assignments become permanent).

The issue with an IFS of : not showing up in echo $IFS is caused by shell parameter expansion and word splitting.

Consider:

$ IFS=:
$ echo $IFS

$ echo "$IFS"
:

When a parameter expansion is not quoted, it undergoes word splitting afterwords. From the manual:

The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting.

and

The shell treats each character of $IFS as a delimiter, and splits the results of the other expansions into words using these characters as field terminators. If IFS is unset, or its value is exactly <space><tab><newline>, the default, then sequences of <space>, <tab>, and <newline> at the beginning and end of the results of the previous expansions are ignored, and any sequence of IFS characters not at the beginning or end serves to delimit words.

So when IFS is a colon, splitting a word consisting of just a colon results in a (single) empty word. Always quote your variables to prevent unexpected gotchas like this.

Solicitous answered 12/7, 2020 at 1:10 Comment(2)
Ahh. This would make the question a duplicate of I just assigned a variable, but echo $variable prints something else.Countless
this is exactly what is written in the link on ubuntuforums I've sent before ;)(ubuntuforums.org/showthread.php?t=1454313 the last post, you need to scroll down). I'm upvoting your answer in recognition of your first initial comment on the question, which, while very simple, contained the answer to the main problem. If you care to put it in the beginning of your answer, I'll also mark it as accepted (in its current form the answer does not answer the main question, so it would be confusing for others to see it as accepted).Histone

© 2022 - 2024 — McMap. All rights reserved.