Showing posts with label bash. Show all posts
Showing posts with label bash. Show all posts

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.

Tuesday, June 29, 2010

PERL: Removing Text Within Parentheses

I recently wanted to get a list of major landmarks, but the text list had the name of the landmark, followed by the location of the landmark in parentheses: ex. Eiffel Tower (Paris, France). I just wanted the names of the landmarks without the text in the parentheses, so had to figure out the command to remove all the parenthesized text.

Perl was the winner as tool of choice. There was a small trick to doing this seemingly trivial task, so document it we shall.

Original: Eiffel Tower (Paris, France)
Desired: Eiffel Tower

There are two ways to do it based on what you want:
perl -p -e 's#\(.*\)##g' textfile
You may have seen 's/oldtext/newtext/g' as the syntax before and are wondering why I am using hash marks (or pound signs) instead. You don't have to use the forward slash, it is just the common way, but if you want to use the forward slash in the search text without having to escape, using hash marks are the way. It can also be used to make it easier to read. Now, onto the command--the \( obviously says look for a left parenthesis, then there is the critical .* which says find any number of any characters. Finally, we close it off with a right parenthesis. This will find anything encapsulated by two parentheses.
perl -p -e 's#\([^)]*\)##g' textfile
This solution will also do the same thing based on our Original text string, but it is slightly different. The [^)] is telling "any character that is not a right parenthesis." The carat (^) is negating everything in the brackets. This is useful if you are making an exclusion set. You can place several characters, [^$)?], and it will look for any character except a $, ), or ?.

Since the two commands work the same for the given example, let's show how the commands will vary in different situations:
If textfile contains
1. Paris (France,) Hilton (Hotel)
2. Paris (France (Hilton) Hotel)
Using
perl -p -e 's#\(.*\)##g' textfile
The results would be:
1. Paris
2. Paris
Note the danger here is that, even though in line 1 Hilton is not in parentheses, it gets removed because there is an ending right parenthesis at the end of the line. This may not be the expected/intended operation.

Next, using
perl -p -e 's#\([^)]*\)##g' textfile
The results would be:
1. Paris Hilton
2. Paris Hotel)
The operation for line 1 may have been what we were expecting, but line 2 doesn't look good. The moral here is to understand what you are trying to do and choose the correct command to do the appropriate operation.