4

I wrote a script (replace.sh) that replaces a ~ with ~\n. I was wondering how can I call this script with multiple arguments.

#!/bin/bash

for i in "$*"
 do
  sed 's/~/~\n/g' "$i"
done

For example I would like to call ./replace.sh text1 text2. It cannot read

the contents of text1 are: 1 ~ 1 ~ 1. After calling the script it should be

1 ~
1 ~
1 ~
1
  • See here for more on the difference between $* and $@: mywiki.wooledge.org/BashGuide/…. It's subtle but very important for shell scripting. Commented Aug 21, 2013 at 21:27

3 Answers 3

4

Use "$@" in your for loop:

for i in "$@"
do
   sed -i.bak 's/~/~\n/g' "$i"
done

Though I am not really sure if sed is doing what you've described here.

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

4 Comments

Exactly what I needed! As for sed, it should be 's/~/\\n/g'
Save some keystrokes by not using in "$@" at all ...
@jiliagre I think this is a case where brevity is worse. Many people won't know what for i alone implies in "$@". It's an extra 7 characters to type, but the payoff in clarity is huge.
Granted. Many people do not know what in "$@" vs the usual ìn $* implies either. That's the reason why I prefer to suggest the empty form which is IMHO easier to remember.
2

This should suffice:

for i do
  sed -i.bak 's/~/~\n/g' "$i"
done

Here a portable variant should work with any posix compliant shell and sed:

for i do
  sed 's/~/~\n/g' "$i" > "$i.bak" && cp "$i.bak" "$i"
done

Note: The in ... part of the shell for loop is optional. When not used, for defaults to pick all the arguments (i.e "$@") which is precisely what you are expecting.

4 Comments

Please don't recommend -i without a suffix. It means no backups. No backups -> very likely ouch. Also, maybe explain what it means if you use a for loop in a shell script with no in <x>?
Answer updated with a more portable solution (POSIX) which handles the backup remark, although running the script more than one will lead to xxx.bak.bak files while the first run will overwrite potential existing backups ...
@jiliagre +1 Thanks for the update/clarification. (I hear you on multiple runs, but still think it's always worth mentioning .bak to new scripters. Pet issue of mine since I first learned Perl one-liners.
Great finer points; editing without a backup hurts.
1

The other answers cover your real problem. But one thing worth saying more about is how $* and $@ differ in expansion when double quoted. tl;dr You almost always want "$@".

"$*" expands to a single string containing all the parameters with the first character of IFS in between them. In practice this usually means all params as one string separated by spaces. So if your params are file1.txt, file2.txt and file3.txt, then "$*" hands that to the loop as one thing, namely file1.txt file2.txt file3.txt. That one thing, of course, does not exist as such.

"$@" expands to each of the parameters, one at a time, treated as a single "word". This is good because it will treat file1.txt, file2.txt and file3.txt from the earlier example correctly as three items. The loop will receive them correctly one at a time. In addition, "$@" is good since even if your filenames have spaces in them, you will get the right behavior since the loop will handle, e.g. filename 1, filename 2, filename 2 and a half as three items, one by one. (Normally the shell treats a space as marking the end of a word, so filename 1 would look like two items to the shell, filename and 1.

When they aren't quoted both $@ and $* expand the words of all the positional parameters. So in that case, they are effectively identical (and both a bad idea since they don't protect you from oddly named files).

There's a good visualization of all possibilities here: http://wiki.bash-hackers.org/scripting/posparams#mass_usage.

3 Comments

I think you should include the double quotes in your answer since the two variables also have meaning (identical meanings, in fact) when unquoted. You might also want to cover the unquoted meanings in the answer. As it stands: $* expands to a single string is incorrect; it should be "$*" expands to a single string ... Yes, you mentioned double quotes in the previous paragraph, but omitting them in the next is misleading.
@JonathanLeffler You're absolutely right. Not sure what's wrong with me. I should include the quotes, and I'll mention the unquoted versions.
Hey telemachus. I had a feeling "$*" did expand to a single string, thanks for confirming and explaining what was happening behind the scenes.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.