Looping in Bash: An Introduction

When I first learned about Linux in the 90’s, I read that it was possible to even write your own commands to use at the command line. Later I learned about bash scripting, and it wasn’t long before I needed to learn how to loop in bash. Looping in bash is one of the fundamental building blocks of bash programming. It isn’t hard to do at all and is worth learning. The main reason to learn looping in bash is to handle doing the same thing over and over again. They’re easy to do even at the command line. Please follow along as we look a couple of basic examples, and how you can expand on them.

Getting Started

Simply put, a loop says “As long as a variable or condition is true, do this. When the variable or condition is not true, stop.” So you have to have a variable, and you have to have something to do while the variable is true.

I’ve generated a list of random names at http://listofrandomnames.com and put them in a file called names.txt. Using a simple loop, we’re going to do some things with those names.

The first part of a loop is defining what to use as data. In this case it’s in names.txt so let’s get that file into a variable.

names=$(cat names.txt)

Using $(command) tells bash to execute that as a command. Now, the contents of names.txt are contained in the variable called names.

Next, we’re doing to define a loop using the variable $names. You’ll notice that we did not use $ when defining the variable, only when calling it. This is called a ‘for’ loop:

for name in $names

Now we’ve started the loop. We’ve defined a new variable here: “name”. It means “For a piece of data in this variable.” We’re going to use $name in the next part:

do echo $name
done

We have to say ‘done’ or we haven’t completed the loop. Lets look at it all together:

#!/bin/bash
names=$(cat names.txt)
for name in $names
do echo $name
done

We started with #!/bin/bash, which tells the shell to execute the script using /bin/bash, but that’s only if we want to use it as a script inside a file. For something this small (and not something I’ll re-use a lot) I just write it on the command line like this:

# names=$(cat names.txt); for name in $names; do echo $name; done

The # signifies the terminal prompt; don’t type that part. The colon separates the elements of the script as if they were on separate lines even though they are not. How does it look when we run it?

# names=$(cat names.txt); for name in $names; do echo $name; done
Tawna
Adam
Lawanda
Klara
Grazyna

Well that doesn’t do much. We could have just done `cat list` and got the same result. Let’s do something more interesting to the names. Looping in bash is a lot more useful than this.

The Main Reason for Looping in Bash

Now to the fun part. Just because we can, we’ll use ‘wc -m’ to find out how many characters are in each name. Notice that we’re using backticks to tell echo to pipe (transfer the output of) the command ‘echo $name’ into the input of ‘wc -m’ which causes ‘wc’ to count the number of letters in the output of ‘echo’. More on pipes later.

# names=$(cat names.txt); for name in $names; do echo $(echo $name | wc -m) $name; done
6 Tawna
5 Adam
8 Lawanda
6 Klara
8 Grazyna

Now that’s more interesting. How about we put them in order, using ‘sort’?

# names=$(cat names.txt); for name in $names; do echo $(echo $name | wc -m) $name; done | sort
5 Adam
6 Klara
6 Tawna
8 Grazyna
8 Lawanda

So now we have the basics of sorting using a loop. Let’s go a bit farther. Let’s sort them, count how many have the same number of letters in them, and sort by how many letters each name has. We’ll use ‘uniq -c’ to find out how many of them are unique, and then sort again. This is a very handy way to create and sort lists.

# names=$(cat 50names.txt); for name in $names; do echo $(echo $name | wc -m) $name; done | sort | awk '{ print "names have " $1 " letters" }' | uniq -c | sort -n
      2 names have 4 letters
      3 names have 10 letters
      3 names have 9 letters
      6 names have 5 letters
     10 names have 7 letters
     12 names have 8 letters
     14 names have 6 letters

We can also express that script as follows, and put it into the following format in a file called ‘namelength.sh’

#!/bin/bash
names=$(cat 50names.txt)
for name in $names
    do 
       echo $(echo $name | wc -m) $name
    done | sort | awk '{ print "names have " $1 " letters" }' | uniq -c | sort -n

You’re looking at the exact same thing. You’d run this by simply typing

bash namelength.sh

and pressing enter. And the reason we have to have that big long line at the end is because we’re using pipe “|” to connect the output of the loop to the input of Sort, then awk, then uniq, then sort again. Each one modifies the input and produces it as output.

While /bin/true; do echo I Love You; done

Bash poetry notwithstanding, you might recall at the beginning that we said that a loop would run as long as the variable was true. In the above case, as long as ‘cat names.txt’ had content that hadn’t been processed, it returns the value ‘true’ to bash. But when it’s empty, it returns ‘false’ and the loop stops. But what if you don’t want the loop to stop?

Let’s use a real world example. In my line of work, I find myself transferring files from flaky connections at times. Often I don’t have access to the status of the server that’s sending the files (often via FTP) so there’s no decent way to check progress of the files. So I’ll use /bin/true as my variable, count the size of the files transferred so far, and then ‘sleep’ to tell it to pause so it doesn’t run out of control. Using ‘sleep’ is important because if you do not, the process will never pause. Depending on what you’re doing, you can exhaust the resources of the machine you’re working on. Trust me.

while /bin/true; du -m --max-depth=1 /path/to/flaky/transfer/files; sleep 30; done

This says “as long as /bin/true is true” (it always is) check the disk usage of 1 level of directories in /path/to/flaky/transfer/files, wait 30 seconds, and then check to see if /bin/true is still true. This way I can see the numbers get bigger every time the ‘du’ command runs. Lather, rinse repeat. To end the loop, just use ‘ctrl+c’ and that will bring you back to a command prompt.

More on variables

For a final exercise we’ll take a look at our first example.

# names=$(cat names.txt); for name in $names; do echo $(echo $name | wc -m) $name; done
6 Tawna
5 Adam
8 Lawanda
6 Klara
8 Grazyna

See how we defined a variable and then used it only once? Defining a variable is great, but if you’re only using it once, why bother making it a variable? It can be expressed more directly this way:

# for name in $(cat names.txt); do echo $(echo $name | wc -m) $name; done
6 Tawna
5 Adam
8 Lawanda
6 Klara
8 Grazyna

You can see how it simplifies the arrangement. When looping in bash right at the command line, I usually do it just like that. If it gets more complicated, I’ll write a shell script in a file and run it as shown earlier.

Another nice thing about looping in bash right at the command prompt is that if you want to see different results, you can just use the up arrow, modify something, and hit enter and get results immediately. For many of the shell scripts I write, I will start them out this way, or figure out a function that way. It makes for quick debugging and learning how to use the commands you need.

I hope you’ve found this article on looping in bash helpful. If you find inaccuracies, errors, or other issues please leave a comment below.

Four Tech Job Stages to recognize for Success

In the past several years This Tech Geek has found himself all over the place with jobs. I had a few different full time jobs in that time,  and I also run my own business on the side. With this unusually rapid cycle, I  was able to spot some trends in myself that led me to good success in my current position. Perhaps you’ll recognize the same Job Stages in your experiences. So I give you Ryan’s Four Job Stages for Success.

Stage 1

Oh no. What have I done. I can’t do this. I’m going to fail!  Welcome to Stage 1. This is where you’re in total freakout mode and am wondering why you chose to inflict this mode of failure on yourself. Don’t worry. You’ll survive, probably. If you don’t, then you might find yourself on the wrong side of a 90 day trial period, or perhaps you’ll just stumble through mediocrity and eventually move on to Stage 2. But there’s a chance that this is going to be an important formative stage. This is the time that you can start some great work habits and learn where (and who) your best resources for knowledge and help are. If you’re an expert in your field, or have many years of experience, Stage 1 might last only as long as you acclimate to your new environment. Companies these days have a tendency to throw people in the deep end after short (or nonexistent) training.

Stage 2

I think I can, I think I can, I think I can. Yeah, you can do it. The Stage 1 jitters are gone and you’re getting your footing and it doesn’t feel like you’ll drown any time soon. You’re on your way to competency! Whether it be because your skill set isn’t up to par with the “old timers” or because you’re still the new kid on the block, Stage 2 is all about learning to stretch your legs a little. There’s no obvious failures on the horizon and you start to feel like you’re going to find success. You are seeing your potential realized. Before you know it you’ll be in…

Stage 3

This is workable. I can do this. I might even be really good at it. Your evaluation is coming up and you’re not dreading it. You’ve been contributing positive things and your coworkers are now your peers. You have gained the respect of your coworkers and supervisors or even management. That promotion you thought was unattainable in Stage 2 is starting to look realistic. People are coming to you for answers and you’re providing them. You’re good at your job and it shows.

Stage 4

I feel comfortable. In fact, I like this and I could probably keep doing this for a long time. This is almost a continuation of Stage 3, with the exception that you’ve made a conscious decision to be here at Stage 4. Perhaps you’ve been promoted to a level you are comfortable with, or are “in the zone” with your current position and are satisfied. This can be wonderful and lead to good job satisfaction.

On the other hand, you might be in Stage 4 because you’re in a dead end job and have been passed up for further promotion, or have lost motivation to move up. Maybe Stage 3 was better for you. Really, it’s up to you to decide why you’re here and what to do about it.

Evaluation

You’ll notice that each phase has its ups and downs. Stage 1 can be very stressful, as can Stage 2. But, so can Stage 4. There’s nothing worse than being stuck in a dead end job with nowhere to go. I once worked in a virtual call center environment and I was stuck in Stage 4 for most of my employment there. When I saw instability in the company, I left. But Stage 4 can also have a lot of positive things about it. For example, if you’ve reached a comfortable level of competency and job satisfaction, and they are both reasonably good, then Stage 4 can last a very long time and be very rewarding. On the other hand If you’re chomping at the bit for something more challenging, then being at Stage 4 can be dangerous for both you and your employer. You’re more prone to go outside the norm to get things done, and your employer is more likely to lose you to a more challenging job. If you’re uncomfortable in Stage 4, then perhaps it’s time to either start hunting for a promotion or a new job.

Conclusion

Certainly there are more nuances to having a job than can be covered in a 825 word article. But these Four Stages are fairly common and I have personally experienced each of them at nearly every job I’ve had. It’s like The Gambler said: “You’ve got to know when to hold ’em, know when to fold ’em, and know when to run.” Recognizing those Job Stages is vital for having success, whether you’re flipping burgers or saving lives.

Have you experienced the same? Leave a comment for us below. We’d love to hear your feedback.

Export Roundcube Contacts to CSV from Database

In this installment I’ll show you how to export roundcube contacts to a CSV file straight from the Roundcube database. Perhaps a customer has moved to a different server, and no longer has access to their roundcube mail. You can restore the Roundcube database, and get the contacts manually.

First, you have to gain access to the MySQL database. I’ll leave that for another time. But lets assume you have it, and you want to export roundcube contacts to a CSV file so that you can import it into another Roundcube installation, or even many other compatible programs.

The first thing you have to do is create a header for the CSV file. CSV means Comma Separated Values. It’s just a bunch of stuff with comma’s in the between the values. That’s it. The first line in a CSV file is the header that contains the names of the columns presented in the following lines. We’re going to use Mozilla Thunderbird’s format for compatibility with Roundcube and other programs.

echo "First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Screen Name,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes," > email.address.contacts.csv

Copy and paste that line, and then you have the header. Now we need to get the content. This is the fun part!


Open up MySQL and select the roundcube database:

root@tidbits [~]# mysql roundcube
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 1068
Server version: 5.5.39-MariaDB MariaDB Server

Copyright (c) 2000, 2014, Oracle, Monty Program Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [roundcube]>

Now we’ll find the user_id of the email address we want to get the the contacts for.

MariaDB [roundcube]> select user_id from users where username='user@domain.com';
+---------+
| user_id |
+---------+
| 59 |
+---------+
1 row in set (0.00 sec)

There it is, ID 59. So now, we’ll export the contacts. Make sure to copy the line exactly, because the sed statements are very important:

mysql roundcube -qbse "select name,email from contacts where user_id=59;" |sed  's/\t/,/g' | sed 's/^/,,/g' >>  email.address.contacts.csv

Now, it’s complete. You can examine the results:

less  email.address.contacts.csv

Because you’ve added the correct headers to export roundcube contacts from the database, and placed the data correctly, you can import the CSV file into any mail program that accepts Thunderbird exports, including Roundcube itself. Enjoy!