Command Line Wizardry, Part Two: Variables and Loops in Bash | GeekComparison

Building commands interactively iteratively is enough to become a command line wizard.
enlarge Building commands interactively iteratively is enough to become a command line wizard.

In our first tutorial on command line wizardry, we covered simple redirection and the basics of sed, awk, and grep. Today we are going to introduce the concepts of simple substitution of variables and loops – again, with a specific focus on the Bash command line itself, rather than Bash scripting.

If you need to write a script for repeated use, especially one with significant logical ramifications and evaluation, I highly recommend using “real language” rather than Bash. Fortunately, there are plenty of options. Personally, I’m a big fan of Perl, partly because it’s available on pretty much every *nix system you’ll ever come across. Others could reasonably choose Python or Go, for example, and I wouldn’t judge.

The real point is that we’re focusing on the command line itself. Everything below is something that you can easily learn to think and use in real time with a little practice.

Set and retrieve values ​​in Bash

Bash treats variables a bit strangely compared to any of the “real” languages ​​I’ve used. Specifically, you need to reference the same variable with a different syntax when setting its value versus when retrieving it.

Let’s look at a very simple example:

[email protected]:~$ hello="Hello, world!"

[email protected]ee:~$ echo $hello
Hello, world!

If you’ve never worked with variables in Bash, you might think there’s a typo in the example above. We set the value of hello but read back its value as $hello† That’s not a mistake – you’re not using the lead $ when you define/set a variable, but you must use it when you read from the same variable later.

Destroy variables

If we need to clear that variable, we can use the unset command – but again, we need to refer to hello instead of $hello

[email protected]:~$ echo $hello
Hello, world!

[email protected]:~$ unset $hello
bash: unset: `Hello,': not a valid identifier
bash: unset: `world!': not a valid identifier

[email protected]:~$ unset hello

[email protected]:~$ echo $hello


Thinking about the error messages we get when we try wrong unset $hello should clarify the problem. When we unset $hellowe pass the where the from hello nasty unset instead of passing the variable name itself. That means exactly what you think it does:

[email protected]:~$ hello="Hello, world!"

[email protected]:~$ myhello="hello"

[email protected]:~$ unset $myhello

[email protected]:~$ echo $hello


Although switching back and forth between references hello and $hello is very disturbing if you are only familiar with “real” languages, Bash is at least relatively consistent about when it uses each style. With a little practice you will get used to this, even if you never feel real comfortable about the.

Variable Range

Before moving on to the basics of variables, let’s talk about the range of variables. Basically, a Bash variable is limited to only the current process. It will not pass your variables to child processes you start. The easiest way to see this in action is by calling bash from bash

[email protected]:~$ hello="Hello, world!"

[email protected]:~$ echo $hello
Hello, world!

[email protected]:~$ bash

[email protected]:~$ echo $hello

[email protected]:~$ exit
exit

[email protected]:~$ echo $hello
Hello, world!

In our original Bash session we have the variable hello Similar to “Hello, world!” and access it successfully with the echo order. But if we start a new one bash session, hello will not be passed to that children’s session. So if we try again echo $hello, we get nothing. But after leaving the children’s scale, we can be successful again echo $hello and get the value we originally set.

If you need to transfer variables set in one session to a child process, you must use the export order:

[email protected]:~$ unset hello

[email protected]:~$ hello="Hello, future children!"

[email protected]:~$ export hello

[email protected]:~$ bash

[email protected]:~$ echo $hello
Hello, future children!

As you can see, export our variable successfully marked hello to pass to child processes – in this case another instance of bash† But the same technique works, the same way, for calling each underlying process that references environment variables. We can see this in action by writing a very small Perl script:

[email protected]:~$ cat perlexample
    #!/usr/bin/perl
    print "$ENV{hello}\n";
    exit 0;

[email protected]:~$ hello="Hello from Perl!"

[email protected]:~$ ./perlexample

[email protected]:~$ export hello

[email protected]:~$ ./perlexample
Hello from Perl!

Like our previous example of a children’s session from bashour Perl script can use our . don’t read hello environment variable unless we first export the.

The last thing we’ll talk about export is that it can be used to set the value of the variable it exports, in one step:

[email protected]:~$ export hello="Hi again!"

[email protected]:~$ ./perlexample
Hi again!

Now that we understand how to set and read values ​​of variables on the command line, let’s move on to a very useful operator: $()

Capture command output with $()

[email protected]:~$ echo test
test

[email protected]:~$ echo $(echo test)
test

The $() command substitution operator captures the output of a command. Sounds simple enough, right? In the example above, we show that the output of the command echo test is of course “test” – so if we… echo $(echo test) we get the same result.

Backticks can produce the same result, e.g. echo `echo test`-but $() is generally preferred, because unlike backticks, the $() operator can be nested.

We can go one step further and assign the output of a command to a variable instead:

[email protected]:~$ du -hs ~
13G	/home/me

[email protected]:~$ myhomedirsize=$(du -hs ~)

[email protected]:~$ echo $myhomedirsize
13G /home/me

Finally, we can combine this with the lessons from our first tutorial and get rid of the first column du‘s output to our variable:

[email protected]:~$ myhomedirsize=$(du -hs ~ | awk '{print $1}')

[email protected]:~$ echo $myhomedirsize
13G

At this point we understand how variables and the $() operator work. So let’s talk about simple loop structures.

For loops in Bash

[email protected]:~$ y="1 2 3"

[email protected]:~$ for x in $y ; do echo "x equals $x" ; done
x equals 1
x equals 2
x equals 3

The above example briefly demonstrates how a simple for loop works in Bash. It cycles through a specified set of items and maps the current item from the list to a loop variable. We used semicolons around the . to separate for loop itself, the do command that marks the inside of the loop, and the done, which lets us know that the loop is over. Alternatively, we could also introduce them as technically separate rules.

[email protected]:~$ y="1 2 3"

[email protected]:~$ for x in $y
> do
> echo "x=$x"
> done
x=1
x=2
x=3

In this example, the leading chevrons aren’t something you type – they’re a prompt provided by Bash itself. This lets you know you’re still in a loop, despite hitting Enter† And that’s important, because you can have as many commands as you want in the loop itself!

[email protected]:~$ y="1 2 3"

[email protected]:~$ for x in $y ; do echo "x equals $x" ; echo "x=$x" ; echo "next!" ; done
x equals 1
x=1
next!
x equals 2
x=2
next!
x equals 3
x=3
next!

As you can see, we can merge as many commands as we want in our for loop. Within the loop, any command can reference the loop variable if desired, or it can ignore the loop variable entirely.

So far we have only looked at numbers in series, but the for walk doesn’t care much for that. Each order of items, in any order, will work:

[email protected]:~$ y="cats dogs bears"

[email protected]:~$ for x in $y ; do echo "I like $x" ; done
I like cats
I like dogs
I like bears

Finally, you don’t need to provide the list with a variable; you can also embed it directly in the loop… but be careful about using quotes, because bash treats a quoted “list” as a single item:

[email protected]:~$ for x in "cats dogs bears" ; do echo "I like $x" ; done
I like cats dogs bears

[email protected]:~$ for x in cats dogs bears ; do echo "I like $x" ; done
I like cats
I like dogs
I like bears

Leave a Comment