How to replace one char to get many strings in Shell?

I can find many questions with answers in the other direction, but unfortunately not in the one I would like to have my replacements: I intend to replace a char, such as #, in a string, such as test#asdf, with a sequence, such as {0..10} to get a sequence of strings, in this example test0asdf test1asdf test2asdf test3asdf test4asdf test5asdf test6asdf test7asdf test8asdf test9asdf test10asdf.

I tried, from others:

  • echo '_#.test' | tr # {0..10} (throws usage)
  • echo '_#.test' | sed -r 's/#/{0..10}/g' (returns _{0..10}.test)
  • echo '_#.test' | sed -r 's/#/'{0..10}'/g' (works for the first, afterwards I get sed: can't read (...) no such file or directory)

What is a working approach to this problem?

Edit, as I may not comment yet: I have to use # in the string, in which this character should be replaced, as the string passed from another program. I could first replace it with another char though.

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

The {0..10} zsh operator (now also supported by a few other shells including bash) is just another form of csh-style brace expansion.

It is expanded by the shell before calling the command. The command doesn’t see those {0..10}.

With tr '#' {0..10} (quoting that # as otherwise it’s parsed by the shell as the start of a comment), tr ends being called with (“tr”, “#”, “0”, “1”, …, “10”) as arguments and tr doesn’t expect that many arguments.

Here, you’d want:

echo '_'{0..10}'.test' 

for echo to be passed “_0.test”, “_1.test”, …, “_10.test” as arguments.

Or if you wanted that # to be translated into that {0..10} operator, transform it into shell code to be evaluated:

eval "$(echo 'echo _#.test' | sed 's/#/{0..10}/')"

where eval is being passed echo _{0..10}.test as arguments.

(not that I would recommend doing anything like that).

Solution 2

You can split the string on the delimiter, capture the prefix and the suffix, then use brace expansion to generate the names:

IFS='#' read -r prefix suffix <<<"$str"
names=( "$prefix"{0..10}"$suffix" )
declare -p names
declare -a names='([0]="test0asdf" [1]="test1asdf" [2]="test2asdf" [3]="test3asdf" [4]="test4asdf" [5]="test5asdf" [6]="test6asdf" [7]="test7asdf" [8]="test8asdf" [9]="test9asdf" [10]="test10asdf")'

Solution 3

Do you have to use #? Maybe you could use %d?

$ for i in {1..10}; do printf "_%d.test " "$i"; done
_1.test _2.test _3.test _4.test _5.test _6.test _7.test _8.test _9.test _10.test

Solution 4

First # starts a comment. So, you need to escape it with \.

Second, use a for loop.

Here is your solution:

for i in {1..10}
    echo '_#.test' | tr \# $i

tr unfortunately does not work for more than one character, such as when you want to substitute # with 10. You are better off using sed for that reason.

for i in {1..10}
    echo '_#.test' | sed "s/#/$i/"

Solution 5

I would do it with parameter expansion:

$ var='test#asdf'
$ for i in {1..10}; do echo "${var/\#/"$i"}"; done

The ${parameter/pattern/string} expansion

  • takes the expansion of $parameter (in our case, $var) and
  • replaces the first occurrence of pattern (the escaped #/# has a special meaning in the context, “replace at the beginning of the string”, which we want to avoid) with
  • string ("$i" in our case)

Alternatively, you could replace the # with %d and use it as the format string for printf:

printf "${var/\#/%d}\\n" {1..10}

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply