0

I am trying to run multiple similar commands using a for loop and string formatting.

I am able to create and then print the strings of the commands I would like to run:

$foo=( "bar" "baz" )
$for ((j=0;j<2;++j)); do printf "touch %s_file.txt; " "${foo[j]}"; done
touch bar_file.txt; touch baz_file.txt;

But I want to enter these strings as a command. Based on other questions I was thinking that eval would do what I need:

$for ((j=0;j<2;++j)); do eval "touch %s_file.txt; " "${foo[j]}"; done
-bash: bar: command not found
-bash: baz: command not found

I was expecting(or hoping) the output would be equivalent to the output of:

$touch bar_file.txt; touch baz_file.txt;

The touch here is just an example. I'm more interested in how to format a string and then run it as a command. Thanks.

4
  • Is there a reason you're iterating by index, rather than for prefix in "${foo[@]}"; do? Shouldn't be any need to have j at all. Commented Jun 10, 2016 at 16:16
  • BTW -- could you be a bit more clear on why you're trying to use string-formatting? It creates a bunch of security exposure you wouldn't have if you did things the best-practices approach. Commented Jun 10, 2016 at 16:17
  • @CharlesDuffy I am fairly unfamiliar with bash so I don't always know what the best practices are. In terms of looping through using a counter, in my actual use case I am looping through multiple arrays, and indexing into them with j. Really I am using string formatting because it is something I know how to do. It's good to know that is not the correct way to perform this operation, I will investigate alternatives. Thanks for your suggestions! Commented Jun 10, 2016 at 16:26
  • Re: iterating over multiple arrays, if your shell is bash 4.3 or ksh93, you might look at using namevars for the purpose. That said, doing that effectively is largely a good case for its own question. Commented Jun 10, 2016 at 18:18

2 Answers 2

4

First, there's no need for using string formatting to generate code here, or in general -- BashFAQ #50 describes the use cases and better-practice alternatives. This could be as simple as the following:

for j in in "${foo[@]}"; do touch "${j}_file.txt"; done

Second, if you must, do it like so:

printf -v cmd 'touch %q_file.txt; ' "${foo[@]}"
eval "$cmd"

This will set cmd='touch bar_file.txt; touch baz_file.txt ', and then execute it.

Use of %q when content is to be later parsed by a shell (as with eval) ensures that your array elements are formatted in a way that will survive shell-parsing -- so if you had foo=( "name with spaces" 'name with $(rm -rf $HOME) dangerous content' ), you would be correctly touching 'name with spaces_file.txt' and 'name with $(rm -rf $HOME) dangerous content_file.txt', not executing the dangerous content and touching multiple files based on the spaces.

Sign up to request clarification or add additional context in comments.

Comments

1

Instead of trying to put the whole command in a string, why not make a function to do the thing you want like:

update_file() {
    touch "$1"
}

foo=( "bar" "baz" )
for ((j=0;j<2;++j)); do
    update_file "$(printf "%s_file.txt" "${foo[j]}")"
done

then you can make that function do arbitrarily complex things with the files

As a general rule, variables hold data, not code. You can sometimes make it work putting code into data, but that way lies dragons. See here for some good reading on that

5 Comments

actually, wait, sorry, I missed that you were taking out the eval. I'm sorry; that comment wasn't applicable.
(rather, without the eval, it would be "$(printf '%s_file.txt' "${foo[j]}" )", with the quotes around the expansion to prevent string-splitting and glob expansion, and %s to emit whitespace &c. literally).
@CharlesDuffy I actually beat you to that one ;)
@EricRenouf thanks for suggestion and link, it is very useful.
...though, update_file "${foo[j]}_file.txt" would presumably do the same thing. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.