5

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 ~
Paolo
  • 1,557
  • 3
  • 18
  • 28
  • See here for more on the difference between `$*` and `$@`: http://mywiki.wooledge.org/BashGuide/Parameters#Special_Parameters_and_Variables. It's subtle but very important for shell scripting. – Telemachus Aug 21 '13 at 21:27

3 Answers3

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Exactly what I needed! As for sed, it should be 's/~/\\n/g' – Paolo Aug 21 '13 at 21:39
  • Save some keystrokes by not using `in "$@"` at all ... – jlliagre Aug 21 '13 at 22:32
  • 1
    @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. – Telemachus Aug 22 '13 at 10:01
  • 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. – jlliagre Aug 23 '13 at 19:43
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.

jlliagre
  • 29,783
  • 6
  • 61
  • 72
  • 1
    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 `? – Telemachus Aug 21 '13 at 21:18
  • 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 ... – jlliagre Aug 21 '13 at 21:30
  • 1
    @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. – Telemachus Aug 21 '13 at 21:34
  • Great finer points; editing without a backup hurts. – Paolo Aug 23 '13 at 17:28
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.

Telemachus
  • 19,459
  • 7
  • 57
  • 79
  • 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. – Jonathan Leffler Aug 21 '13 at 21:45
  • @JonathanLeffler You're absolutely right. Not sure what's wrong with me. I should include the quotes, and I'll mention the unquoted versions. – Telemachus Aug 21 '13 at 22:16
  • Hey telemachus. I had a feeling "$*" did expand to a single string, thanks for confirming and explaining what was happening behind the scenes. – Paolo Aug 23 '13 at 21:20