Scripts-R-Us

Introduction

What are scripts?

by Macedonio Fernandez

Scripts are plain text files where we put together any number of commands. Of course, those commands require to be ordered in a specific form, but the good side of this is that the syntax that we have to learn is relatively simple, as long as our aims are not too ambitious.

What can a script do for me?

It can sort out in different folders a list of selected files; it can output through a pop-up the fan speed at a given moment; or it can send a daily e-mail to the present administration of the United States telling them about the disasters of our local economy. The beautiful thing about scripts is that they let you fully customize your environment and occasionally share the scripts you have written to make other people's lives easier.

In this issue, we will provide a very simple script (with two variants) that allows us to extract any number of archives that we select in Konqueror.

This script has two main advantages: it is an alternative to ark's service menu, which does not seem to handle well multiple extractions; it identifies correctly (as opposed to ark) which files are password protected and asks us to input the password. However, the most important goal is to get acquainted with the basics of bash scripts.

PART 1: Understanding a sample script

In order to understand how the sample scripts work, we are going to explain briefly what the commands used actually do, and how they are used.

basename

It outputs the name of a given file, not its full path. It can also echo the name of the file without the extension, provided that we specificy the extension in the command line.

Example: if we have a file the full path of which is /home/john/download/random.txt, we can execute the following command:

$ basename random.txt

This will give us the following output: random.txt.

If we want to get the name of the file, but removing its extension, we specify the extension after the name of the file preceded by a dot:

$ basename random.txt .txt

This will give us the following output: random

FOR (DONE)

FOR is a command (actually a loop) that tells the interpreter to run the commands that follow for each member of a group. What is this group? It could be group of files selected in konqueror (as we will see in the sample scripts), a group of files specified in a list (as in the example below), a list of names, a sequence of numbers, etc.

Example:

for i in ( random.txt michael.pdf lars.odt ); do
  cp $i /home/john
done

This script would copy all the files in the group enclosed in parentheses to /home/john. However, it would not do so as a bulk, but rather one by one.

This is because FOR is a loop, that is, it starts a cycle with all the elements that have been selected. In the above example, it would first take random.txt, copy it to /home/john, take michael.pdf, copy it, etc.

Although this is certainly useless as is, the advantage of the FOR loop is that it allows you to process each file in turn: it can be used it to sort out different types of files in directories, remove certain files and make backups of others, etc. (This will prove useful in our sample script).

What is important here is that in the first line, we have assigned to i the value of what is after the = sign. In each loop, the expression $i will represent each of the elements. In the first run $i will be equal to random.txt, in the second run, it will be equal to michael.pdf, and so on.

The expression done indicates the the loop has finished and that, if if restarts, $i will be reset and will take on a new value.

IF (THEN); ELSE, ELIF

As its name suggests, the IF command states a condition that has to be satisfied for something else to take place; for instance: if a given number is even, then call you mother-in-law; if a given file is empty, then remove it; if the temperature of the hard disk is above 50°, then output a warning. It is always closed by FI.

ELIF / THEN provides a second condition to be satisfied if the first one hasn't, and then specifies what to do if this second condition is satisfied. For instance, this will be useful in the sample script to do the following: we take a group of archives selected in konqueror which we want to extract; the if command will specificy that if the file is a .rar archive, then it has to do something; else, if the file is a .7z archive, it has to do something else.

ELSE instructs what to do in case none of the conditions previously stated by the if (or if+elif) statement is not satisfied, without specifying any further coinditions.

Example: we have 15 files, 5 of them are .rar, 5 are .tar.gz, 2 are pdf, 3 are txt. We write a script that specifies the following: IF a certain file is a .rar archive, then extract it: ELIF the file is a .tar.gz archive, then extract it and remove the original archive; ELSE, move the files to another directory. This last statement will apply to all the files in the input that do not satisfy any of the conditions stated by IF and ELIF statements.

Example:

for i in ( random.txt michael.pdf lars.odt morris.jpg morris.jpg ); do
  if [[ $i == *.txt ]]; then
   cp $i /home/john/Text_files/
  elif [[ $i == *.pdf ]]; then
   cp $i /home/john/pdfs/
  else cp $i /home/john/other_files/
  fi
done

Explanation of the scripts

This first script extracts all selected archives in the current directory, provided that they are .zip, .rar, .tar.gz, .tar.bz2 or .7z archives (regardless of whether the names of the archives or its extension are in upper or lower case).

SCRIPT 1

1  #!/bin/bash -x
2  FILE=$1
3  cd "${FILE%/*}"
4  for i in "$1"; do
5  echo -e "\n\nTrying to extract $i"
6  if [[ "$i" == *.[Rr][Aa][Rr] ]]; then
7  unrar x -ad "$i";
8  elif [[ "$i" == *.7[Zz] ]]; then
9  7za x "$i";
10  elif [[ "$i" == *.zip ]]; then
11  unzip "$i";
12  elif [[ "$i" == *.ZIP ]]; then
13  unzip "$i";
14  elif [[ "$i" == *.[Tt][Aa][Rr].[Gg][Zz] ]]; then
15  tar -xf "$i";
16  elif [[ "$i" == *.[Tt][Aa][Rr].[Bb][Zz][2] ]]; then
17  tar -xf "$i";
18  fi
19  done

Let's explain what the most important lines in this script do.

1) #!/bin/bash

This first line is mandatory in every bash script. It instructs what interpreter to use (in our case: bash).

2) FILE=$1 3) cd "${FILE%/*}"

These two lines tell the script to move its focus to the directory where the selected files are. If we didn't do this, all files would be extracted to your home dir.

$i stands for the first element in the input; in our case, the first of the selected files. However, it stands for the full path of the file (/home/john/download/random.txt), not only its basename (random.txt) or its path (/home/john/download/).

So what we have done here is assign that input to the variable FILE that we will use in the next line.

The second line strips the name of the archive from the full path of the input and leaves only the directory where it is located; hence, cd "${FILE%/*}" changes directory to the path of the input file.

4) for i in "$1"; do

This is the start of the FOR loop. $i becomes the total input (all the files selected through konqueror), and i becomes the value taken in each loop. In our case, the FOR command will restart the loop for each of the files we have selected, processing each of them separately.

5) echo -e "\n\nTrying to extract "$i""

Prints out a message that takes the value of $1 in each loop.

6) if [[ "$i" == *.[Rr][Aa][Rr] ]]; then

Here starts the IF statement. IF the input file is equal to *.rar or *.RAR or *.rAr, etc. (the square brackets help to tell the interpreter that the condition is satisfied when any of the combinations of the enclosed letters appear in the extension of the archive), THEN do something, in this case:

7) unrar x -ad "$i";

This commands extracts the .rar file (represented by "$1") in the current directory. Now comes a third element in the FOR command: ELIF, which stands for "ELSE IF". What does it do? If the first condition is not satisfied (that is, if the file is not a .rar file), it will present another condition to be satisfied:

8) elif [[ "$i" == *.7[Zz] ]]; then

What this and the rest of the following lines do (lines 10-17) should be now clear. In this case we are saying that, if the first condition was not satisfied but this one is, THEN:

9) 7za x "$i";

Extract the .7z file.

18) fi

This line closes the IF statement.

19) done

This line closes the FOR loop. Although the previous script is perhaps the most useful and simple alternative, it has one drawback: if the person who compressed the archive did not organize the files into directories, your current directory could end up looking like a complete mess. To avoid that problem, you can use the following script.

SCRIPT 2

1    #!/bin/bash -x
2    FILE=$1
3    cd "${FILE%/*}"
4    for i in "$1"; do
5    echo -e "\n\nTrying to extract "$i""
6    if [[ "$i" == *.[Rr][Aa][Rr] ]]; then
7    unrar x -ad "$i";
8    elif [[ "$i" == *.7z ]]; then
9    file_base_name='basename "$FILE" .7z'
10   echo -e "\n\nCreating directory $file_base_name"
11   mkdir "$file_base_name"
12   cd "$file_base_name";
13   7za x "$i";
14   elif [[ "$i" == *.7Z ]]; then
15   file_base_name='basename "$FILE" .7Z'
16   echo -e "\n\nCreating directory $file_base_name"
17   mkdir "$file_base_name"
18   cd "$file_base_name";
19   7za x "$i";
20   elif [[ "$i" == *.zip] ]]; then
21   file_base_name='basename $FILE .zip'
22   echo -e "\n\nCreating directory $file_base_name"
23   mkdir "$file_base_name"
24   unzip "$i" -d "$file_base_name";
25   elif [[ "$i" == *.ZIP ]]; then
26   file_base_name='basename $FILE .ZIP'
27   echo -e "\n\nCreating directory $file_base_name"
28   mkdir "$file_base_name"
29   unzip "$i" -d "$file_base_name";
30   elif [[ "$i" == *.tar.gz ]]; then
31   file_base_name='basename "$FILE" .tar.gz'
32   echo -e "\n\nCreating directory $file_base_name";
33   mkdir "$file_base_name";
34   cd "$file_base_name";
35   tar -xf "$i";
36   elif [[ "$i" == *.TAR.GZ ]]; then
37   file_base_name='basename $FILE .TAR.GZ'
38   echo -e "\n\nCreating directory $file_base_name";
39   mkdir "$file_base_name";
40   cd "$file_base_name";
41   tar -xf "$i";
42   elif [[ "$i" == *.tar.bz2 ]]; then
43   file_base_name='basename $FILE .tar.bz2';
44   echo -e "\n\nCreating directory $file_base_name";
45   mkdir "$file_base_name";
46   cd "$file_base_name";
47   tar -xf "$i";
48   elif [[ "$i" == *.TAR.BZ2 ]]; then
49   file_base_name='basename $FILE .TAR.BZ2';
50   echo -e "\n\nCreating directory $file_base_name";
51   mkdir "$file_base_name";
52   cd "$file_base_name";
53   tar -xf "$i";
54   fi
55   done

Let's comment on some of the new lines:

6)  if [[ "$i" == *.[Rr][Aa][Rr] ]]; then
7)  unrar x -ad "$i";

The command unrar (when used with the modification -ad) extracts the content of the archive with a full path. However, other archive types don't have that option. So, what we will do is make a directory with the same name as the archive, change directory and extract the files there.

8)  elif [[ "$i" == *.7z ]]; then
9)  file_base_name='basename "$FILE" .7z'

Here we are assigning to the variable file_base_name the results of running the command called basename to the present instance of i. We have enclose $FILE in quotes so as to avoid problems if the name of the file has spaces. If the selected file which is now being processed is /home/john/download/polka.7z, the result of basename "$FILE" .7z will be polka. Now file_base_name=polka (but only for this run of the loop).

11) mkdir "$file_base_name"
12) cd "$file_base_name";
13) 7za x "$i";

What we do here is evident: we create a directory with the name of the present value of the variable we defined before; we change to the new directory and extract the files there. The following lines (14-53) do the same as before for the rest of the archive types. (Every line is duplicated because the command basename does not work well with extensions in square brackets).

PART 2: Putting the script in action

What do we do now?

  1. Create the script
    1. Create an empty text file.
    2. Paste the contents of one of the above scripts into the file.
    3. Save it (for example: multiple_extract_nofolder.sh).
    4. Make it executable. The simplest way to do this is: right click on the created file and select "Properties". In the tab "Permissions" choose "Is executable".
    5. Create a directory where you will store a backup of your scripts. (eg: ~/.scripts)
    6. Copy the script to that directory and to /usr/local/bin (you wil have to be root to do that).
  2. Create a service menu to run the script.
    1. Go to ~/.kde/share/apps/konqueror/servicemenus
    2. Create a new text file and call it "multiple_extract_nofolder.desktop" (or whatever you want, provided that you use the extension ".desktop") and open it with an editor (kate, kwrite, etc.).
    3. Open the new file and paste the following:
      [Desktop Entry]
      Actions=Extract_Multiple_nofolders
      ServiceTypes=all/allfiles
      X-KDE-Priority=TopLevel
      [Desktop Action Extract_Multiple_nofolders]
      Name=Extract archives here
      Icon=package
      Exec=konsole -e bash
      multiple_extract_nofolder.sh %U
      
    4. Save the file.
    5. Go to a directory where you have more than one archive.
    6. Select the archives to extract, right click on the selection, choose "Extract archives here".

NOTE 1

A useful alternative when testing scripts is to modify the exec line to look like this:

Exec=konsole --noclose -e bash multiple_extract_nofolder.sh %U

The --noclose option will, as is evident, leave konsole open so that you can see the output of each command and determine where things went wrong, in case something has failed.

NOTE 2

In the .desktop file we placed the following line:

X-KDE-Priority=TopLevel

This lines places the service menu in the first column that appears as soon as you right click on a file. If we remove it, the service menu will appear under "Actions". The choice will depend on how often you will use the script.

NOTE 3 (Dependencies)

Both scripts depend on the folowing applications (available thorugh Synaptic): unrar unzip p7zip tar. If they are not installed, you can get them via pasting the following in the command line as root:

$ apt-get install unrar unzip p7zip tar

Links:

Introductions to Bash
http://www.tldp.org/LDP/Bash-Beginners-Guide/html/
http://tille.garrels.be/training/bash/
http://www.linuxtopia.org/online_books/bash_guide_for_beginners/
http://linux.die.net/Bash-Beginners-Guide/
Creating KDE Service Menus
http://developer.kde.org/documentation/tutorials/dot/servicemenus.html
http://techbase.kde.org/Development/Tutorials/KDE3/Creating_Konqueror_Service_Menus

Top