3

I'm learning the shell, and I want to be able to loop over some variables. I can't seem to find anywhere where anyone has done this so I'm not sure it's even possible.

Basically I just want to save myself trouble by using the same sed command on each of these variables. However the code obviously doesn't work. My question is, is it possible to loop over variables and if not how should I be doing this?

title="$(echo string1)" artist="$(echo string2)" album="$(echo string3)" for arg in title artist album do $arg="$(echo "$arg" | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g')" done 

here is the error:

line 12: syntax error near unexpected token `$arg="$(echo "$arg" | sed -e 's/&/\&amp;/g' -e 's/</\&lt;/g' -e 's/>/\&gt;/g')"' 
2
  • What's the error when you run the code? (Assuming you're referring to something that the syntax highlighting didn't already point out to you...) Commented Jun 18, 2011 at 8:00
  • Another common way to do this type of problem is to do something like: Commented Jun 19, 2011 at 9:42

1 Answer 1

5

Your problem isn't with the loop, it's with the assignment. The variable name needs to be literal in an assignment, i.e. you can write title=some_value but not $arg=some_value.

A portable way to assign to a variably-named variable is to use eval. You also need to obtain the value of $arg (not just the value of arg, which is $arg), which again requires using eval.

new_value="$(eval printf %s \"\$$arg\" | …)" eval $arg=\$new_value 

Another way to assign to a variably-named variable that's specific to bash/ksh/zsh but won't work in plain sh is to use the typeset built-in. In bash, if you do this in a function, this makes the assignment local to the function. To obtain the value of the variably-named variable, you can use ${!arg}; this is specific to bash.

typeset $arg="$(printf %s "${!arg}" | …)" 

Other problems with your snippet:

  • title="$(echo string1)" is a complicated way to write title="string1", which furthermore may mangle string1 if it contains backslashes or begins with -.
  • You need a command terminator (; or newline) before the do keyword.

If you're relying on bash/ksh/zsh, you can make the replacements inside the shell with the ${VARIABLE//PATTERN/REPLACEMENT} construct.

title="string1" artist="string2" album="string3" for arg in title artist album; do eval value=\$$arg value=${value//&/&amp;} value=${value//</&lt;} value=${value//>/&gt;} eval $arg=\$value done 
Sign up to request clarification or add additional context in comments.

2 Comments

this is fantastic. I tried it out and actually it didn't quite work (I'm trying the portable route). Here's what I came up with: new_value="$(eval printf %s \"\$$arg\" | sed -e 's/&/\\&amp;/g' -e 's/</\\&lt;/g' -e 's/>/\\&gt;/g')" eval arg=\$new_value I had to add a second $ to the printf statement or else new_value would equal "title" "album" or "artist". I also removed the $ from the second eval statement or else that would equal "title" etc.. oh and for curiosity's sake why does there have to be a \$ before new_value on the second line as opposed to just $?
@Dennis: Sorry, the missing $ in the printf call was a typo. I don't see anything wrong with any of the others, which one did you mean? In eval $arg=\$new_value, the string to evaluate is something like title=$new_value, which assigns the value of new_value to the variable title. If you wrote eval $arg=$new_value, the string to evaluate would be e.g. title=Apostrophe ('): the new value would be used as shell code, not as a literal string.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.