,________________, |----------------| || o , , , , \/ | ||_ | |\| |_| /\ | ;::::::::::::::::; .--. | | | | '--:-------> BASH Tips for the Advanced Noob | '----------> By me, KLAATU! BASH is cool, but for it to be amazing you really have to start dipping into the complex stuff. That's the hard part, of course, because as usual when you ask some random unix guru for help, they say things like "It's simple, just use i for the variable in a for loop." Generally I don't even bother pretending to be appreciative with brilliant answers like that. So, here's a unix noob's explanation of this BASH stuff, or at least the basic advanced stuff. So, first of all, if you don't already know it, learn emacs. Vi vs Emacs arguments aside, sitting in front of a default BASH terminal and being able to jump to the beginning of a complex command, kut the line, issue something else instead, and then yank it back again, is supremely satisfying and powerful. Secondly, understand that variables exist in BASH and how to use them. p------------------q | bash$ i=hello | | bash$ echo $i | | hello | b------------------d See what happened? we set the variable "i" to be "hello" and then we told BASH to echo, as a variable "i" and so BASH returned the word "hello" Turns out you can set anything to be a variable in BASH. Let's set the word "iph0ne" as a variable: p-------------------------------q | bash$ iph0ne=/dev/null | | bash$ echo whut > $iph0ne | | bash$ | b-------------------------------d What happened there? Well, we set the string iph0ne to equal the location /dev/null, which as everyone knows is oblivion on a unix system. So basically we made "iph0ne" equal a completely empty void (which also happens to be how you feel after you've purchased one). The we sent the word "whut" to $iph0ne, meaning in fact we sent the word "whut" into oblivion. And that is exactly why BASH returned a new bash prompt, because it did what we told it to do: echo "whut" into the great /dev/null and then move on with life. Now you understand variables. Good thing, because the next example uses variables. Thirdly, learn the difference between these three characters: ` ' " They look deceptively similar but in fact are different. Try these examples with me: p:-------------:|xterm|:--------------:q | bash$ i=gnu | | bash$ echo $i | | gnu | b--------------------------------------d OK, that was expected behaviour, yes? Next: p:-------------:|xterm|:--------------:q | bash$ echo `$i` | | bash: gnu: command not found | | | b--------------------------------------d Darn, something went wrong? Well actually, no; BASH did exactly what we told it to do, which is what that ` symbol means - and just to be clear, that's the little accent mark just under the ~ (tilde) on a standard US keyboard. What this does is tells BASH to: 1. resolve any commands inside the quotes 2. process the command with these resolved values. So it literally first figured out that $1 was "gnu", and then it tried to process "gnu" as a command, at which point it stalled because it has no command named gnu. That all comes in handy when you actually do want to perform some action on the result of something else...I can't really think of a cool complex example at the moment but here's a simple one. If we have a text file that holds an IP adress, and we want to ping that IP address....well, watch: p:-------------------------------:|xterm|:-------------------:q | bash$ echo "google.com" > host.txt | | bash: ping `cat host.txt` | b-------------------------------------------------------------d What do you think will happen? If you guessed that BASH will first perform the cat, then you are correct: p:-------------------------------:|xterm|:------------------------:q | PING 72.16.223.102 (172.16.223.102) 56(84) bytes of data. | | 64 bytes from 72.16.223.102: icmp_req=1 ttl=64 time=0.035 ms | | 64 bytes from 72.16.223.102: icmp_req=2 ttl=64 time=0.032 ms | | 64 bytes from 72.16.223.102: icmp_req=3 ttl=64 time=0.040 ms | | ......... | b------------------------------------------------------------------d OK, onto a different kind of quote....this is the single quote. the ' just under the " on a US keyboard. This one is like a force field. It is the polar opposite of the apostrophe. It doesn't let BASH resolve anything: p:-------------:|xterm|:--------------:q | bash$ echo '$i' | | $i | b--------------------------------------d See? told you. It takes whatever is between the quotes and preserves it exactly as it is. If you're crazy like me, you could even try issuing this dreaded command: p:-------------:|xterm|:--------------:q | bash$ echo 'cp -r the internets' | | cp -r the internets | b--------------------------------------d Luckily we had that in single quotes, so the internets are not copied to your drive. But also notice that BASH didn't even care that there was a space between "the" and "internets" which, of course, is unusual because usually BASH interprets a space as a command separator. But since it's all in single quotes, BASH sees everything in the quotes as a blob. It just spits back exactly what's written. And finally there is the normal quotation mark...the " on your keyboard. These, BASH sees as a blob BUT with the ability to resolve variables. Watch: p:-------------:|xterm|:--------------:q | bash$ echo "cp -r the internets" | | cp -r the internets | b--------------------------------------d Ok, that behaved exactly like the single quote. But if we insert our handy variable in there: p:-------------:|xterm|:--------------:q | bash$ echo "cp -r $i internets" | | rm -r gnu internets | b--------------------------------------d So BASH saw the common BASH character of $ and knew that this meant there was a variable in that blob, so it resolved the variable and processed the command. And now you understand the complexity of various quotes. Good luck with them. And finally, there are some pretty amazing things you can do with "for" loops in BASH, and you don't even have to drop into vim (er I mean emacs) to write it out in a shell script. Real life example. I had about 50 chapters of a book, each in its own text file. I knew that the first line of each chapter was the chapter title. I figured there had to be some way to get BASH to go into each file, grab the first line, and then place that line in a list called chapters.list You emulate this with me by creating five files with one word on the first line, and then random text the lines thereafter. Something like this: p:---------------:|xterm|:---------------------:q | bash$ echo "tale of two cities" > 1.txt | | bash$ dmesg >> 1.txt | b-----------------------------------------------d Now if you less that file, you'll see that on the first line there is "tale of two cities" and afterwards are kernel messages. Do that a few times if you want, and then you'll have a few text files with unique strings on the first line. Now to do a "for" loop such that we can grab the first line and dump them into a list. Let's think this through. So we have, let's say, 5 .txt files in a directory. They each have different file names but they are ALL .txt files. Therefore, we could use this magic phrase: for i in *.txt Which means "So...BASH, I want you to grab onto a file name that is not constant but I can tell you that the file will be called "something" dot txt." Which results in BASH looking at the first .txt it finds, which in this case would be 1.txt, and irregardless of the part that is variable (1, in this case), it takes the file name and assigns it to our variable. Ergo, it is doing this: p------------------q | bash$ i=1.txt | b------------------d But then what does it do? Well, nothing yet. It just holds onto "1.txt" as the value of our variable $i. To get it to do more, we need to add a semi-colon after the statement, which in BASH language means to continue on with the rest of the command. Then we use the next part of this magical phrase: do echo `head -n1 $i` >> chapters.list THIS means to echo [whatever the stuff between the apostrophes works out to be] into a file called chapters.list So, first BASH does the work and figures out that $i is really "1.txt", and then it sees that we want it to take the head of that file (head -n1) and hold it for us. So now BASH sees this statement: echo 'tale of two cities' >> chapters.list which is simple enough; we want BASH to echo 'tale of two cities' into chapters.list and, as usual, if something is already in chapters.list just add this line add the bottom of the list. If that file doesn't exist yet, create it for us. Bash does that and then......we need a semi-colon for it to know to continue onto the next step. So if you're following along, our command so far looks like this: p-----------------------------------------------------------q | for i in *.txt; do echo `head -n1 $i` >> chapters.toc; | b-----------------------------------------------------------d And absurdly enough, all we actually have to do at this point is add the word done to let bash know that if it has grabbed ALL the variable parts of ALL the files in this directory, and it's taken the head -n1 of each one and dumped that into chapters.list, then it's done. That's kinda what they mean with the whole "for" loop thing; "for" means: as long as you find a new file to do [whatever] to, then keep doing it, but once you've checked all those things off, then you're "done". OK, so the full command is: p-----------------------------------------------------------------------q | bash$ for i in *.txt; do echo `head -n1 $i` >> chapters.toc; done | b-----------------------------------------------------------------------d And sure enough, if you have a bunch of text files called something.txt and you run that command on it, you will have a list of all the first lines of the files, one after another, in a new file called chapter.list Summary: So, the "for" command is a little abstract to me. I mean, if you give me a command like bash$ cat host.txt Then i know that we are going to cat the file contents of host.txt But bash$ "for" i in *.txt frankly boggles my mind. So I take it on faith. I know that if I tell bash for [VARIABLE] in *.txt then it will figure out that I mean to store "WHATEVER.txt" as the value of the VARIABLE I give it. Understand that you don't have to use the lower case letter "i" although to be honest that seems to be very common, so you might want to get used to that. the "do" parts of the equation is simple, if you know enough about BASH to construct a slightly advanced command. Just remember that by the time BASH gets to the "do" part, it has obtained its variable value for the Nth file, so you don't have to think about WHICH file you're dealing with; the same thing will happen to EACH file in the directory that matches your criteria. The "done" part is really easy. And that's it! Hope this has been helpful. It sure did save me from taking ten minutes to manually list all the chapter headers into a table of contents, and figuring it all out only took me four hours! Man, I love saving myself work. EOF....for now!