Friday, July 30, 2010

LINUX: Repeat Command/Action N Times in VIM Editor

All too often I find myself redoing a command over and over. I've often said, there must be a better way to do this. And there is! While writing some automation scripts in an agile "paired-programming" method yesterday, I learned a few neat tricks for VI from my programming partner. I'll share them in the next few posts.

The first is repeating a command. If, for example, you want to delete 100 lines from your file in VIM. Pressing the "d" key twice deletes a single line. So I used to just keep typing dd 100 times till everything I want was deleted. If you know how many times you want to run the command, you just type the number first, then the command.
100dd
Typing the above in VIM will repeat the single line delete (dd) command 100 times. How else can it be use? It can even insert text repeatedly! Type the following
50iTHE END IS NEAR<escape><escape>
The 50i tells VIM that you are going to insert some text that you want repeated 50 times. The text "THE END IS NEAR" is what will be repeated. And pressing the escape key twice implements it. You would get:
THE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEARTHE END IS NEAR
Notice it is all one line. In order to make it appear on multiple lines, just press the enter key and then end of the text, before the escape. Very simple.

Thursday, July 29, 2010

LINUX: Alias and Script Subshells

This is an update to my last post on Scripts and Alias. Almost immediately after posting about the solution of enabling the expand_aliases shell option in scripts, I tried it in a more complicated script with a subshell, and it didn't work.

I'll make this post quick. In a script, you can run commands in a subshell (remember, the script itself is already in a subshell). This sub-subshell is not really a subshell, because if you echo the $SHLVL environment variable within it to see what shell level you are at, you will see that it is at the same level of the script. However, we are going to consider it a sub-subshell because it doesn't influence the rest of the script with what it does or sets. You run these sub-subshells by encapsulating them in parentheses. Eg:
#!/bin/bash
shopt -s expand_aliases
alias testme1="echo It Worked"
alias
testme1
( shopt -s expand_aliases
echo "I am in a subshell!"
echo "Can anyone hear me?"
alias testme2="echo Subshell Alias"
testme2 )
If you run this, you will see that testme1 works, while testme2 doesn't. Even putting the shopt -s expand_aliases within the sub-subshell still doesn't fix it.

SO, the solution? I went back to my old dirty hack, and that worked fine.
#!/bin/bash
shopt -s expand_aliases
alias testme1="echo It Worked"
alias
testme1
( shopt -s expand_aliases
echo "I am in a subshell!"
echo "Can anyone hear me?"
alias testme2="echo Subshell Alias"
`alias | grep testme2 | cut -d\' -f2` )

One caveat to point out: If you don't have an alias set, this will mess up things. I tried running a "make" command using my above hack, but in one case, the build environment didn't bother setting up an aliases "make", working fine with just a standard "make". When it tried my hack, it didn't find anything in the alias, so did nothing.

Tuesday, July 27, 2010

LINUX: Scripts and Alias

This one stumped me for a while; we had build scripts that kept failing, yet if you manually executed the commands from the script, everything worked fine. Eventually I discovered that it wasn't properly executing the "make" command as was setup in the alias. When we set up our build environment, we alias "make" to have a bunch of compiler flags so when you finish running the setupenv file (to set environment variables and such), all you have to do is run "make" and the alias keeps track of all the necessary build flags.

Scripts never work properly because they interpret your "make" command to be just that--"make" with no build flags. It turns out this is because scripts (non-interactive shells) (1) don't expand aliases and (2) don't bring in the current environment.

Concerning point 2 about the current environment: This is actually a good thing. Imagine if you have a special environment you set up and the script runs fine in your area, then someone else tries to run it in theirs and it fails. It would be a pain to begin debugging by comparing what is in your .bashrc file and what you did to customize your environment. A script should be self contained, able to run in any environment. So when you run it, it doesnt carry over your environment variables into its non-interactive subshell that it runs in.

We are more concerned with point 1 though: A shell script, at least on all the Fedora and RedHat servers I've worked on, do not expand the aliases by default. Before I came across the good solution on the internet, I hacked together my own solution of parsing the expanded command out of the alias output:
`alias | grep make | cut -d\' -f2`
That works, but is not an elegant solution by any means. Recently a coworker was having trouble with a build script and I got into explaining to him he may have a problem with a non-expanding alias. I showed him my solution and then gave the caveat that there is probably a better, easier solution out there. Then I looked for one and found it (don't ask me why I didn't find it the first time when I created my hack solution). Just place the following command in the begginning of your shell script:
shopt -s expand_aliases
What this does is sets the shell option (shopt) to enable (-s) the expansion of aliases (expand_aliases). The man page says that this option is enabled by default for interactive shells, so the implication (and reality we've witnessed) is that it is disabled by default for non-interactive shells.

If you want to see this in action, try this script:
#!/bin/bash
alias testme="echo It Worked"
alias
testme
The result will print the new alias you assigned (typing alias in line 3 prints out current aliases) for testme. Notice there are no other aliases set, not even the ones that are set globally for the system. The execution of the testme command fails with "command not found" because it is not recognizing the alias.

Now try again with the shopt set:
#!/bin/bash
shopt -s expand_aliases
alias testme="echo It Worked"
alias
testme
Now it will work perfectly, showing the alias we set and echoing the "It Worked" statement.

Wednesday, July 14, 2010

LINUX: The Power of HERE Documents

After doing things the manual, brute force way, you will be able to truly appreciate the elegance of HERE documents. If you wanted to create a file with a few lines of text in them, you would redirect the echo command to it. E.g.
echo "The old method" > temp.txt
echo "Appending a second line to the file >> temp.txt
Side note--a single redirect arrow, the "greater than" sign, will create a new file called temp.txt containing the text on the left hand side. The double redirect arrow as shown on the second line, will not create a new file (nor erase the contents of the existing one), but rather, it will append to it.

So that method is okay for doing a few small things, but it isn't efficient or powerful when your needs are greater. We can use a HERE document to put several lines of text into a file without having to continuously echo and redirect. A HERE document can be used for more that just this, but that is the focus of this post. So, the example is as follows:
cat > temp.txt << EOF
The Old Method
Appending a second line to the file
EOF
The temp.txt and EOF can be changed to whatever you want. The temp.txt is just a file to dump the text into. The EOF is a label to indicate the beginning and end of the HERE document. Notice that there is a matching EOF at the end. If you wanted to use the word HERE, that is perfectly fine, just make sure that at the end of the section to put into the file, you write a matching HERE to indicate the end of the block.

The end result of the two methods are the same, however the HERE document scales much better. You can put a 100 lines of text right in there if you desired with little effort.

There is even more power to be had here. We can do things like executing commands at run time when compiling the HERE document. E.g.
cat > tmp1 <<EOF
this is the first file
this is the 2nd line of 1st file
`echo hello`
did the above come out as "hello" or echo hello?
no, it executed the echo and just printed hello...nice
EOF
Using the technique of appending mentioned above, along with the HERE document, we can also grow our HERE document. E.g.
cat > tmp1 <<EOF
this is the first file
this is the 2nd line of 1st file
`echo hello`
did the above come out as "hello" or echo hello?
no, it executed the echo and just printed hello...nice
EOF

cat >> tmp1 <<EOF
did everything get deleted?
nope, it appended
EOF
The results of the above script would be (inside tmp1 file):
this is the first file
this is the 2nd line of 1st file
hello
did the above come out as "hello" or echo hello?
no, it executed the echo and just printed hello...nice
did everything get deleted?
nope, it appended