shell

A Safer Way to Return the Current Username in Shell Script

Getting the current username, it sounds like a very easy task:

1
2
$ bash -c 'echo "$USER"'
chao

But depending on the environment variable is not reliable, it can be easily overridden:

1
2
$ USER=foo bash -c 'echo "$USER"'
foo

Or just simply fail to work:

1
$ docker run --rm bash:4.4.23 bash -c 'USER=foo echo "$USER"'

The USER environment was not set in /etc/profile in this case:

1
2
3
4
5
6
$ docker run --rm bash:4.4.23 bash -c 'cat /etc/profile'
export CHARSET=UTF-8
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PAGER=less
export PS1='\h:\w\$ '
umask 022

This is especially important when writing shell script. You do not want to end up with a wrong username.

One solution is to use whoami, which prints the effective user:

1
2
$ USER=foo bash -c 'whoami'
chao

Another solution is to use id command:

1
2
$ USER=foo bash -c 'id -un'
chao

This will eliminate the overridden environment variable issue. Also, check out this StackOverflow post for others and POSIX compatible means:

https://stackoverflow.com/questions/19306771/get-current-users-username-in-bash

How to Correctly Use Environment Variables on the Command Line

I always forget about how to use shell environment variable correctly. I think because I did not grasp the key concept. For example, here is the wrong approach:

1
2
$ FOO=foo BAR=bar echo foo is ${FOO} and bar is ${BAR}
foo is and bar is

The correct answer I was hoping for is:

1
foo is foo and bar is bar

Because what I frequently do is use environment variable inside shell script:

1
2
3
4
5
#!/usr/bin/env bash
#
# script.sh
echo foo is ${FOO} and bar is ${BAR}

then do:

1
2
$ FOO=foo BAR=bar ./script.sh
foo is foo and bar is bar

This works as expected because the environment variables are passed into the subshell that executing the script.

But why not the first or the wrong approach?

The reason is that shell expands the variable before the command being executed. To shell, the first approach looks like:

1
$ FOO=foo BAR=bar echo foo is and bar is

The environment variables FOO and BAR were expanded before the command was executed. Hence, no values were printed.

Non-Interactive Redis Install

To build Redis from source, we first need to install TCL 8.5 or newer, this is needed for make test later:

$ sudo apt-get install -y tcl

Now clone and make:

$ git clone https://github.com/antirez/redis
$ git checkout 2.8.13
$ make
$ make test
$ sudo make install
$ sudo utils/install_server.sh

Binary (redis-cli and redis-server) will be installed into /usr/local/bin.

The last command with utils/install_server.sh is an interactive command. Since the script is a shell script using the read built-in command and -p option for prompt, we can make it non-interactive by redirecting input from echo command:

$ echo -n | sudo utils/install_server.sh

Without pumping any value into the script, the default values are used.

If really want to customize it, we can add our own values:

$ echo -e \
  "${PORT}\n${CONFIG_FILE}\n${LOG_FILE}\n${DATA_DIR}\n${EXECUTABLE}\n" | \
  sudo utils/install_server.sh

There are 6 read statements, hence n - 1 newline characters. Without using -n, the last newline character is supplied by echo.

Here are the default values:

PORT=6379
CONFIG_FILE=/etc/redis/6379.conf
LOG_FILE=/var/log/redis_6379.log
DATA_DIR=/var/lib/redis/6379
EXECUTABLE=/usr/local/bin/redis-server

The utils/install_server.sh script should return something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Welcome to the redis service installer
This script will help you easily set up a running redis server
Selecting default: 6379
Selected default - /etc/redis/6379.conf
Selected default - /var/log/redis_6379.log
Selected default - /var/lib/redis/6379
Selected config:
Port : 6379
Config file : /etc/redis/6379.conf
Log file : /var/log/redis_6379.log
Data dir : /var/lib/redis/6379
Executable : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
System start/stop links for /etc/init.d/redis_6379 already exist.
Success!
/var/run/redis_6379.pid exists, process is already running or crashed
Installation successful!

Set an alias for Redis client:

$ cd /usr/local/bin && sudo ln -s redis-cli redis

For more advanced install, see the README file.

Why Shebang?

Frequently the initial two characters on the initial line of a script are: #!.

Why Shebang? Well, simple reason, because shell needs to know which interpreter to use when executing the script.

The sha-bang (#!) at the head of a script tells your system that this file is a set of commands to be fed to the command interpreter indicated. The #! is actually a two-byte magic number, a special marker that designates a file type, or in this case an executable shell script (type man magic for more details on this fascinating topic). Immediately following the sha-bang is a path name. This is the path to the program that interprets the commands in the script, whether it be a shell, a programming language, or a utility. This command interpreter then executes the commands in the script, starting at the top (the line following the sha-bang line), and ignoring comments. - Starting Off With a Sha-Bang

The shebang line is usually ignored by the interpreter because the # character is a comment marker in many scripting languages. Even the actual script is written with a different commenting character, such as in JavaScript:

1
2
#!/usr/bin/env node
// JavaScript stuff starts here.

Because the first line is interpreted by the shell, and the rest is passed into JavaScript interpreter, in this case, Node.

The syntax of shebang:

#! interpreter [optional-arg]

Whether there is a space between shebang character and the interpreter or not, it does not matter.

The interpreter must usually be an absolute path to a program that is not itself a script. The following are example interpreters:

1
2
#! /bin/sh
#! /bin/bash

More examples are via head -n 1 /etc/init.d/*.

The optional‑arg should either not be included or it should be a string that is meant to be a single argument (for reasons of portability, it should not contain any whitespace). - Shebang_(Unix)

We also frequently see the following shebang:

1
#!/usr/bin/env bash

That has to do with portability. Not every system install node or bash command in the same path. /usr/bin/env will search through user’s $PATH, and correctly locate the executable.

To conclude for Node, here is the format I am using:

1
2
3
4
5
6
7
8
#! /usr/bin/env node
// Title
// =====
//
// Markdown style description starts here.
'use strict';