Posts Tagged ‘Command-line interface’

thinkering with bash

Thursday, July 2nd, 2009
Bash
Image via Wikipedia

Here is another post from the land of bash/shell scripting, this time instead of focusing on usability of tips the focus is thinkering

sudo echo 1500 > /proc/sys/vm/dirty_writeback_centisecs
bash: /proc/sys/vm/dirty_writeback_centisecs: Permission denied

I bet you wanted to do something like that? A naive user would think this will write 1500 in that file, well, that’s not the case :-) . What really happens here is that bash first redirects the stdout that file (meaning that the redirection is performed as current user) and only than executes the command (which is executed as root). An ugly workaround or this kind of a problem is to create a script and than sudo execute that, a quicker workaround is to pass shell string argument:

 sudo bash -c 'echo 1500 > /proc/sys/vm/dirty_writeback_centisecs'

Here are two links really worth reading (which i think belong in the category of “best practices”):

Everyone (hopefully) knows not to use `ls`, but what are the cases when the alternative is just too painful? So far i found only one, when you want to do something (to which the chronological order matters) with rotated logs, web servers usually give you a bunch of files like access.log.15.gz access.log.7.gz access.log.12.gz.

for i in access.log*.gz ; do gunzip "$i"; done
for i in `ls -v access.log* | tac`; do cat "$i"; done

First step in processing should be obvious, we simply decompress the gzipped files. As a side note, gunzip is retarded :-) , it can’t handle multiple files at once and it forces you to use .gz extension. The second line is very unportable, it will work just on the GNU coreutils (due to ls’s -v), what it does is print you the web servers logs as web server would write them, suitable for any kind of post processing. A real world use case would be you have logs stored this way but haven’t run webalizer (or whatever) on it.

Let’s have a look a close look at find(1)’s execution of non trivial commands over the matching files. You can ofcourse take easy way out and create a script, but real man use bash -c!

find . -exec bash -c 'for i; do echo "b00$i"; done' -- {} +

The interesting bits here are:

  • We are using ‘+’ to terminate the expression, that tells find “accumulate as many files as possible (on linux: getconf ARG_MAX) and than pass them for one execution”
  • ‘–’ is here for bash, it tells it “stop taking command line switches”
  • -c argument should be quoted using single quotes (’), with double quotes (”) things like $i would be expanded by current shell before they even get to the invoked shell. In this example the output would be just a bunch of “b00″’s
  • It appears that we are not looping through anything, bash implicitly loops through positional parameters if nothing is passed to for keyword
  • {} should be quoted to work with files with spaces, right? wrong! :-) The steps that happen here are:
    • bash sees the {}, it doesn’t mean anything to it so it passes it as is to find
    • find sees the {} and expands it to file (list) and passes it to bash -c, it doesn’t know or care what are the special characters for shell. So event if the file name has any special characters find passes it as is to bash -c
    • the invoked bash get’s the arguments and works with them
Reblog this post [with Zemanta]
Blog Widget by LinkWithin