2

This what I am trying to get in a bash shell script:

for a in "1" "2 3" "4 5 6"
do
  echo "a: $a"
done
a: 1
a: 2 3
a: 4 5 6

But the quotes are treated differently when you use a variable:

echo "\"1\" \"2 3\" \"4 5 6\"" > a.txt

cat a.txt
"1" "2 3" "4 5 6"

read aline < a.txt

If you double-quote the variable, you get this:

for b in "$aline"
do
  echo "b: $b"
done
b: "1" "2 3" "4 5 6"

And without quotes you get this:

for b in $aline
do
  echo "b: $b"
done
b: "1"
b: "2
b: 3"
b: "4
b: 5
b: 6"

Is there a way to get the for to process the contents of the variable as in the first example?

4
  • Is there another issue that makes you ask this question? Do you have another problem that requires you to disable the shell's standard quote removal, which happens during the evaluation of a command?
    – Kusalananda
    Feb 21, 2019 at 6:43
  • @Kusalananda yeah, the OP said something to the effect that they simplified the example too much and here were the real thing, but it was cut short at the ":", and the message was immediately deleted. Let's hope that nothing bad happened to him <;-\
    – user313992
    Feb 21, 2019 at 7:19
  • This is my first time posting a question on this forum, so working out my bugs around posting/commenting. The first answer I got from mosvy gave me enough to work out a solution. The "set -- " seemed to be losing fields. It doesn't like having '#Mi' (no quotes) as one of the fields but once I realized that, I just ran it through sed and now I'm getting what I want. Thanks for the help. Feb 21, 2019 at 7:46
  • @LinusHicks If you're happy with one or several of the answers, upvote them. If one is solving your issue, accepting it would be the best way of saying "Thank You!" Accepting an answer also indicates to future readers that the answer actually solved the problem.
    – Kusalananda
    Feb 21, 2019 at 8:10

2 Answers 2

2

If you're OK with having the whole package (variable/arithmetic/command expansion, globbing, etc) on top of quote processing, this will do:

echo '"a b" "x         y"' >a.txt

eval "set -- $(<a.txt)"
for b do
    printf 'b: %s\n' "$b"
done

b: a b
b: x         y

Change the $(<a.txt) to $(cat a.txt) if you want it to work in a regular shell, not just bash (or other advanced shells like zsh or ksh). You could only use this if you're sure you can control the content of a.txt, otherwise it will be trivial to exploit your script via splitting with ;, command expansion, etc.

However, if you just want to pass those arguments to a command, you can use xargs, which will just process the quotes, without doing any expansion:

xargs <a.txt printf 'b: %s\n'

But xargs will only work with external commands, not with shell functions, will ignore backslashes inside double quotes just like inside single quotes, and won't be able to parse multiline strings.

If these latter limitations are OK, you can still use xargs as a quote parser via a wrapper or an array:

mapfile -t args < <(xargs <a.txt printf '%s\n')
for b in "${args[@]}"; do
        printf 'b: %s\n' "$b"
done
4
  • Note that $(<...) is a ksh operator, copied by zsh and bash (though bash still forks a process, so only save the cat execution there when using it). See Understanding Bash's Read-a-File Command Substitution for details. Feb 21, 2019 at 6:33
  • @StéphaneChazelas included both suggestions.
    – user313992
    Feb 21, 2019 at 6:45
  • @StéphaneChazelas no, it's not: printf '"a\nb" c' | xargs => xargs: unterminated quote, xargs: unmatched double quote, etc. Also the standard says: A string of zero or more non-double-quote ( '"') characters and non- <newline> characters can be quoted by enclosing them in double-quotes. Same thing for single quotes.
    – user313992
    Feb 21, 2019 at 7:11
  • Oh yes, you're right, for xargs a newline is quoted with backslash only. I forgot about that, sorry. Feb 21, 2019 at 7:15
1

If instead of bash you're using ksh93 (ksh93 is the shell bash tries to mimic anyway), you could use the -S option of its read builtin which is to read csv (so understands csv-style quoting):

IFS=" " read -rSA array < a.txt
printf 'a: %s\n' "${array[@]}"

With zsh, you could use it's z parameter expansion flag which is used to tokenise the content of a variable as if it was zsh code (and so understand quoting as zsh would, but not do the code evaluation that eval would do) along with the Q parameter expansion flag to remove one layer of quoting:

IFS= read -r line < a.txt
printf 'a: %s\n' "${(Q@)${(z)line}}"

(the @ parameter expansion flag is to preserve empty elements, reminiscent of ksh's ${array[@]} behaviour).

If a.txt contains more than one line, with zsh you can replace the read command with content=$(<a.txt) and with ksh93 use read in a while loop (each read invocation potentially reading more than one line if a quoted string spans several line).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .