On Fri, Mar 14, 2025 at 01:34:48 +0100, Steffen Nurpmeso wrote: > a() { > echo $#,1="$1"/$1,2="$2"/$2,3="$3"/$3,4="$4" > echo $#,'*'="$*"/$*, > } > set -- '' 'a' '' > #IFS=\ ; echo "$*"$* $*; a "$*"$* $*;unset IFS > #IFS= ; echo "$*"$* $*; a "$*"$* $*;unset IFS > IFS=:; echo "$*"$* $*; a "$*"$* $*;unset IFS > > outputs > > :a: a a > 4,1=:a:/ a ,2=a/a,3=/,4=a > 4,*=:a::a::a/ a a a, > > I have a problem ^ with this space character of bash.
There's so much noise here. All I want to see is ONE command that produces unexpected output. Nothing else. Now, let me see if I can strip down your example to reproduce your result. hobbit:~$ cat foo #!/bin/bash IFS=: a() { printf '%d args:' "$#" printf ' <%s>' "$@" echo echo $#,'*'="$*"/$*, } set -- '' a '' a "$*"$* $* hobbit:~$ ./foo 4 args: <:a:> <a> <> <a> 4,*=:a::a::a/ a a a, This is the same as the output you got, yes? A slash, a space, an "a", two spaces, another "a", two spaces, a final "a", a comma and a newline. I added code to show the arguments being passed into the function, since for some reason you introduced a function and another round of expansions into the picture. So, let's start at the global scope. You're permanently changing IFS to a colon, and you're defining 3 positional parameters: the empty string, the letter "a", and another empty string. Next, you're calling the function "a" with 4 arguments: <:a:> <a> <> <a> Inside the function "a", with IFS still permanently changed to colon, you're calling echo with a series of arguments that are the result of multiple expansions concatenated together. This mess needs to be dissected very carefully. echo $#,'*'="$*"/$*, So, we have: $# unquoted; it produces strictly numeric output, and IFS is : so there are no surprises here the three literal characters ,*= "$*" quoted the literal character / $* unquoted the literal character , 1) The $# expands to "4", which becomes the first character of the first argument word. 2) The three literal characters ,*= are appended to that. 3) "$*" is quoted, so it expands to the single word ":a::a::a" and this is appended to the first argument word. 4) The literal character / is appended. At this point, the first argument word is <4,*=:a::a::a/> 5) $* appears unquoted. Now things get tricky. We generate a string by concatenating all the positional parameters together with : (which is the first character of IFS) between them. Doing this gives us the string ":a::a::a" just like in step 3. However, since this expansion is unquoted, it undergoes a round of word splitting, and a round of pathname expansion. There are no globbing characters in this string, so I'll omit the pathname expansion step. Each instance of : in the string causes a word split to occur, so let's proceed left to right. We start with one word which is the empty string. The first character of the input string is ":" so we close off the first word (empty), and begin a second word (also empty for now). The next character is "a", so we append that to the second word. The third character is ":", so we close off the second word and begin a third word (empty for now). The fourth character is ":", so we close off the third word and begin a fourth word (empty for now). The fifth character is "a", so we append that to the fourth word. The sixth character is ":", so we close off the fourth word and begin a fifth word. The seventh character is ":", so we close off the fifth word and begin a sixth word. The eight and final character is "a", so we append that to the sixth word. All together, then, the unquoted expansion gives us six new words: <> <a> <> <a> <> <a> But remember, we started out with a growing argument word: <4,*=:a::a::a/> The six new words are added to this, by appending the first new word to the growing argument word, and then adding each additional word as a new argument word. The first new word is empty, so there is no change made to the first argument word. The new set of argument words after step 5 is therefore: <4,*=:a::a::a/> <a> <> <a> <> <a> 6) After the unquoted $* you have a "," character, which is appended to the last argument word. After this step, we have the final set of argument words: <4,*=:a::a::a/> <a> <> <a> <> <a,> 7) These argument words are passed to the echo command. echo will potentially evaluate these as options, or process backslash combinations within each argument. However, none of our words begin with "-" or contain backslash, so we can set aside the concern of echo mangling the arguments. echo will print each argument word, with a space character between each pair of argument words, and with a newline added to the end. There are 6 argument words, so echo will add a total of 5 spaces and one newline. We get the following string from echo (plus a newline): <4,*=:a::a::a/ a a a,> There are two spaces after the fourth "a" because there's one space added between <a> and <>, and a second space added between <> and the next <a>. The same reasoning applies to the two spaces between the fifth and sixth "a"s. echo prints the "a" argument (arg 4), then a space, then the empty string argument (arg 5), then another space, then the final "a," argument (arg 6). Do you see how horrifyingly *messy* this is? This is why we don't play with unquoted parameter expansions resulting in word splitting. Just stop doing it. PLEASE.