Line-by-line and pipe-by-pipe explanation:
#!/bin/bash
This is a so-called shebang - basically tells it to run this script with the program /bin/bash
.
cat $1
$1
is the first script argument. cat $1
is going to output the contents of the file supplied as first script argument to stdout. However, since there's a pipe after this, the stdout will be piped to stdin of the next command in the pipeline, grep
in this case.
grep "^$2[0-9]*.*$3 +[0-9]*\ [0-9]*\ [0-9]*"
This will read the stdin (what cat $1
output above). You can read about grep more e.g. here:
The above will filter the lines supplied to it by regex. The regex:
^$2[0-9]*.*$3 +[0-9]*\ [0-9]*\ [0-9]*
basically says that we want lines that:
- Start with the second script argument (
$2
above),
- Then have zero or more digits (
[0-9]*
)
- Followed by zero or more occurrences of (almost) any character
- Followed by the third script argument (
$3
)
- Followed by two or more spaces (
+
- note that there are two spaces here)
- Followed by zero or more digits (
[0-9]*
)
- Followed by a space (
\
)
- Followed by zero or more digits (
[0-9]*
)
- Followed by a space (
\
)
- Followed by zero or more digits (
[0-9]*
)
All lines that match the above will be output to stdout. Again, stdout is piped to the stdin of the next command, sed
in this case.
sed "s/\([0-9]*\)\t\(.*\)\t\(.\).*\t\(.*\)\t\(.\).*\t+[0-9]*\ [0-9]*\
\([0-9]*\)/\5\1 \2 \3 \4 \6/"
You can read about sed
more e.g. here:
The above basically says, for each line:
- Substitute (
s/
)
- (A) This:
\([0-9]*\)\t\(.*\)\t\(.\).*\t\(.*\)\t\(.\).*\t+[0-9]*\ [0-9]*\([0-9]*\)
- (B) With this:
\5\1 \2 \3 \4 \6
The part marked with (A) above is the regexp again, similar to what grep used. What it says is a bit more regular. Note that it has a structure along the lines X\tY\tZ\t...
. What this essentially says to sed
is - match a line that has tab characters (this is what \t
means) and some things (X
, Y
, Z
) in between. Those things in the above can be grouped in two ways:
- Expressions such as
\([0-9]*\)
are so-called regex capturing groups. They are basically delimited by parentheses, except that sed
is a bit older than what is the usual way to do in regexps today. E.g. if you used a regex tool such as http://regexpal.com/, you'd put ([0-9]*)
instead. sed
needs these to be escaped to signify groups - otherwise it would think it needs to match the actual parentheses. It can be instructed to do the reverse by supplying -r
command line option
- Expressions outside of the escaped parentheses (e.g. the part
\t+[0-9]*
)
The capturing groups is what allows sed
to do what you are asking for. Note the (B) part of the sed
command. It says this:
\5\1 \2 \3 \4 \6
This is actually a nice way of saying - replace what I matched in this line the 5th capturing group, then 1st group, then a space, then a 2nd group, etc.
In order to make it clearer, here's a sample command for you to try:
echo abc|sed 's/\(.\)\(.\)\(.\)/\3\2\1/'
or if you want in a extended regex format which is easier to read:
echo abc|sed -r 's/(.)(.)(.)/\3\2\1/'
Run it and see what this outputs - noting that echo outputs three characters in the line and that for sed
part .
matches (almost) any character, it should be clear what and how it applies to your situation. I suggest you to play with some examples of sed substitution on the Net - that should be the best way to clear things up.