You want to learn bash? Here is an excellent starting point.

http://mywiki.wooledge.org/BashPitfalls

Start there, and you will quickly overtake your peers who try on bash for years by reading the man-page and complaisant tutorials.

Advertisements

From http://xonsh.org/faq.html

Even though I have been using BASH for well over a decade, I am not even sure I know how to add two numbers together in it or consistently create an array.

And instead of whining they come up with a serious alternative. Its a python-shell that attempts to mimic some important aspects of linux-shells. You can install it via pip.

Container types like mappings are mainly for people who are weak and tent to lose track. We avoid structured types because they make things unnecessarily simple. On the other hand bashs associative arrays come with a special syntax, so there is no danger people understand your code easily.

Lets create an associative array.

declare -A A=([a]=3 [b]=6 [c]=14) 

How do we get all keys a, b, c from A?

$A[{@!}]
$!A{[@]}
${A[@]!}
${!A[@]}
${A![@]}
${@A[!]}
${!A[]@}

Hints:

  • As discussed earlier, to get all values we write ${A[@]}.
  • With ${!X} we refer the value of the variable with name $X

In an earlier post we discussed processing the output of the find command, e.g. a list of paths. Since a path may contain spaces using just whitespace as separator between the list entires wont always work.

A common solution is the one given here. The bash variable IFS (“Internal Field Separator”) is set to newline and then set back to the original value in order to avoid trouble that may arise from changing this important parameter.

#!/bin/bash
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in *
do
  echo "$f"
done
IFS=$SAVEIFS

Why is the output of echo is used for setting the IFS to newline instead of a plain "\n"? I do not know but I am afraid of the reasons there might be for this. Anyway, setting and setting back that variable is somehow crooked. What if the command within the for-body relied on the preset value of IFS?

Dealing with containers such as lists or arrays you may want to code a condition like ‘does the array A contains an element (equal to) e’.
How to do this in bash? There are vivid discussions about this issue on relevant sites. As stated by tokland at superuser.com:

It’s an old question.

A frequently proposed solution is this.

A=(4 12 35 43)
e=12
if [[ ${A[@]} =~ $e ]] ; then
    echo "e=$e is in A"
fi

It works fine under certain conditions.

e=5 is in A

If you actually want to rely on the outcome you should iterate over the array, see check-if-an-array-contains-a-value. This comes quite handy and you can even save some lines by using ;.

rush is an ruby-shell designed for command-line tasks such as file and process handling.

In lieu of bash command sequence joined by pipes, you subsequently apply methods or refere to members on the object yielded before. The following example start with a filtered list of the files in the directory some_dir.

some_dir['**/*.rb'].search(/^\s*class/).lines.size 

This has several advantages:

  • Presuming you are in ruby you can fully use your ruby tools-set.
  • You can do one-liners subsequently processing arbitrary objects.
  • For more complex cases you have a descent programming language at hand.

There are downsides:

  • In order to call other system commands you have use bash-wrapper command.
  • The project does seem to be maintained at the moment and there is very little documentation. The above example, though taken from the projects home page, does not seem to work.

It is a good approach to start with reasonable programming language and then make some alternations in order to get a useful shell. Maybe ruby is not the very best choice as base language because it has a bit particular syntax that might be unnecessarily confusing to non-expert users.

The return statement is for passing a return code, e.g. number between 0 and 255. If you want to output a string you have to do a echo within the function body. There are two issues with that.

First, you cannot distinguish between the output the function returns and some information you want to print for instance for debugging while the body is executed.

Assume a function list_certain_pdfs that computes a list of all pdf-files that contain a certain string and can be found below a base-directory passed as input.
For each directory below the base-dir that contain at least one fitting pdf the function should also print the number of found pdfs to stdout while running.

A=$(list_certain_pdfs /var/run/bkm/)
/var/run/bkm/tmp/00/: 3
/var/run/bkm/tmp/08/: 120
/var/run/bkm/tmp/ext/: 1

This should not be difficult to do, but with bash it is.

Second, it is not clear how pass an array as output of a function. As we already know, structured data types do not belong to bashs assets. One reason arrays are rarely used might be that you cannot simply return them from a functions body.

A way to deal with the situation is pointed out here. It is a valid solution but it is also a kludge.