Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm attempting to parse to output from git status --porcelain -b for use in my prompt but I'm coming across strange behaviour when performing parameter expansion.

This snippet should demonstrate the issue:

#!/bin/bash
IFS=$'\n'
touch ab
status_arr=( $(git status --porcelain -b) )
for (( i=0; i<${#status_arr[@]}; i++ )); do
    echo ${status_arr[$i]}
    echo ${status_arr[$i]:0:2}
done

When run in a clean git directory, I get this output:

$ . sandbox/statusline/issue.sh 
## master...origin/master
##
?? ab
ab

I would expect ?? to be echoed on line 4 of the output and indeed if I change the script to touch abc or even touch a, that is what I get.

I'm really confused by this, I guess I must be missing something obvious in bash but google isn't yielding anything useful.

If this is a known "thing", is there a way I can get around it/avoid it completely?

share|improve this question
1  
I am not really sure if I understand your question, but with man bash and /\? you can search all uses of ? in bash. There are a few that might interfere with what you are doing. –  Bernhard Aug 5 at 19:12
    
+1 for the man page tip but I had looked for something like that but the only thing I could see was ${parameter:?word} and I'm not getting the error I'd expect from that. –  Skyfall Aug 5 at 19:15
1  
The proper way would be bash script. –  Bernhard Aug 5 at 19:24
1  
I am not sure what can go wrong (better experts around here), but don't source unless you explicitly want that. Does it make a difference for the problem you are experiencing? –  Bernhard Aug 5 at 19:30
2  
@Bernhard "Don't source unless you explicitly want to" is good advice. For example, sourcing the above snippet sets IFS, an important variable that affects other aspects of shell operation, to a non-default value. In this case however, it doesn't affect OP's question, but I would recommend putting IFS back anyways. –  jw013 Aug 5 at 19:35

2 Answers 2

up vote 4 down vote accepted

The ? is a shell glob character used to match file names. It matches a single character. Thus, since you have a file named ab, the ?? pattern matches it.

The reason this happens is because your parameter expansion is NOT quoted.

share|improve this answer
    
Thanks, that solves it. –  Skyfall Aug 5 at 19:35

An unquoted variable or command substitution is not interpreted as a string, but as a list of filename wildcard patterns. That is, the value of the variable or the output of the command is split into separate pieces separated by characters in IFS (this step is called field splitting); then each piece is interpreted as a wildcard pattern, and if the pattern matches some files, then it's replaced by the list of matching file names, otherwise the pattern is left intact (this step is called filename generation).

For example, status_arr=( $(git status --porcelain -b) ) sets status_arr to the one-element array containing the 5-character string ?? ab' because IFS contains only a newline and there is no file matching the pattern ?? ab. If IFS had its default value containing a space, then status_arr would be set to the two-element array containing two occurrences of the 2-character string ab.

If the variable or command substitution is in double quotes, then the resulting string is used as is: field splitting and filename generation only apply to unquoted substitutions.

You can disable filename generation completely by running set -f. This is useful if you want to take advantage of IFS splitting. Note that set -f completely disables filename generation, not just on the output of substitutions: set -f; echo * always prints *.

#!/bin/bash
IFS=$'\n'
set -f
touch ab
status_arr=( $(git status --porcelain -b) )
for (( i=0; i<${#status_arr[@]}; i++ )); do
    echo "${status_arr[$i]}"
    echo "${status_arr[$i]:0:2}"
done

(Here, with filename generation still disabled, and given that the elements of status_arr cannot contain characters from IFS by construction, it's safe to leave out the double quotes in the echo statements. However, this is very fragile — it heavily relies on the way the array was constructed and in the fact that the status of set -f and the value of IFS haven't changed since then. Always use double quotes around variable and command substitutions unless you have a good reason to leave them out and you know that it's ok to do so.)

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.