1. Specify the bash

The first line of the shell script, #! What should come after that? If you ask this question to others, different people may give different answers.

I’ve seen /usr/bin/env bash, I’ve seen /bin/bash, I’ve seen /usr/bin/bash, I’ve seen /bin/bash, I’ve seen /usr/bin/env sh. This is the programming equivalent of the “four hui”.

In most cases, all five are equivalent. But, as anyone who has written a program knows, “a few cases” often hide unexpected potholes.

What if your system’s default shell is not bash? For example, for a version of a Linux distribution, sh is not bash by default.

What if the system’s bash is not in /usr/bin/bash?

I recommend /usr/bin/env bash and /bin/bash. Env adds an intermediate layer that lets env search for bash in $PATH; The latter is the officially endorsed, conventionally bash location, to which /usr/bin/bash is nothing more than a symbolic link.

2. set -e 和 set -x

OK, after some discussion, now the first row is settled. Should I start the second line?

Wait a minute! Insert a line of set-e and a line of set-x before you start thinking and writing down the actual code logic.

Set-x prints out the contents of each line of the shell script. It allows you to see the current execution and the variables involved are replaced with the actual values.

Set-e terminates the program if execution fails, just like “throwing an exception” in other languages. (To be exact, not all errors end the program, see note below.)

Note: Set-e terminates the program under complicated conditions. In Man Bash, a full paragraph is used to describe various scenarios. Most implementations will exit on error, unless the shell command is in the following case:

  1. The end part of a pipeline, such as the error | ok
  2. A combined statement of end, such as ok && error | | the other
  3. The non-ending part of a string of statements, such as error; ok
  4. It is inside a judgment statement, including test, if, while, and so on.

The combination of these two can save you a lot of time during debugging. For defensive programming reasons, it is necessary to insert them before writing the first concrete line of code. Ask yourself, when you’re writing code, how many times can you get it right at once? Most code is usually debugged and modified repeatedly before it is committed. Rather than introducing these two configurations in the middle of a battle, it’s better to leave debug leeway in the first place. After the code is finally ready to commit, it’s not too late to consider whether to keep it.

3. Take shellcheck

Ok, so NOW I have three lines of code, not one line of business logic. Is it time to start writing?

Wait a minute! To do a good job, he must sharpen his tools. This time, I will introduce a shell scripting artifact: ShellCheck

I’m ashamed to say that despite years of shell scripting, I still can’t remember some of the syntax. At this time will rely on shellcheck to point out.

In addition to alerting you to syntax problems, ShellCheck also checks for common bad code in shell scripting. In my original N suggestions, there were several more about these bad codes, but shellCheck could find out these problems completely, so I reluctantly excluded them. There’s no doubt that using ShellCheck has given my shell writing skills a huge leap forward.

The so-called “standing on the shoulders of giants”, although we new recruits, skills are not as strong as veterans, but we can catch up with each other in equipment ah! Move start to install, you can get to know a good “teacher”, why not? Shellcheck, by the way, is written in Haskell. Who says Haskell is only for bitches?

4. Variable expansion

In shell scripts, you occasionally see something like this:

echo $xxx | awk/sed/grep/cut…

What looks like a big situation is just changing the value of a variable. Why kill a chicken with a sword? Bash’s built-in variable expansion mechanism is already there for all your needs! Read the f**k manaul! Man bash then searches for Parameter Expansion, and here’s the trick you want. Keywriters have also written a related article that they hope will help: playing with the Bash variable

5. Pay attention to the local

As you write more and more code, you start distilling repetitive logic into functions. Chances are you’ll fall into one of bash’s pits. Under bash, variables are global by default without the local qualifier. Variables default to global — similar to JS and Lua; But relatively few bash tutorials tell you this right from the start. In a top-level scope, it doesn’t matter whether a global variable is a global variable. But declaring a global variable in a function can contaminate other scopes (especially if you don’t even notice it). So, be sure to include the local qualifier for variables declared in functions.

6. The trap signals

If you’ve ever written a slightly more complex program that runs in the background, you know what “signals” are in the POSIX standard. If you don’t know, go straight to the next paragraph. Like other languages, the shell supports processing signals. Trap SIGHandler INT The sighandler function can be called when a SIGINT is received. The same goes for capturing other signals.

However, the main application of trap is not to catch any signal. The trap command enables you to “capture” many different processes — specifically, it allows you to inject function calls into a particular process. Trap Func EXIT and Trap Func ERR are the most common.

Trap func EXIT allows the function to be called at the end of the script. Since the registered functions can be called with or without a normal exit, in cases where a cleanup function is called, I use it to register the cleanup function instead of simply calling the cleanup function at the end of the script.

Trap Func ERR allows a function to be called if a runtime error occurs. A common technique is to use the global variable ERROR to store ERROR information and then report the ERROR based on the stored value in the registered function. Bringing otherwise fragmented error-handling logic into one place can sometimes work wonders. Remember, however, that both the functions registered with EXIT and those registered with ERR are called when the program exits unexpectedly.

7. Look before you leap

The above are all concrete suggestions, and the remaining two are not important.

The name of this advice is “Think before you act”. In fact, no matter what code you write, even if it’s just an auxiliary script, think twice and don’t be careless. No, keep that in mind when writing scripts. After all, many times a complex script starts with a few lines of commands. The person who wrote this script at first probably thought it was a one-off task. Code inevitably makes assumptions about external conditions, which may be normal at the time, but as the external environment changes, these become hidden reefs. To make matters worse, few people test scripts. You don’t know if it works until you run it.

To slow down the rot of script code, you need to identify at the time of writing what is becoming dependent and what is essential to the proper running of the script. Have appropriate abstractions and write code that can be changed; Also, be defensive and give your code a moat.

8. Play to your strengths and avoid your weaknesses

Sometimes scripting in the shell means it’s hard to port, hard to uniformly handle errors, and hard to manipulate data cleanly.

While it’s easy and quick to implement complex functions using external commands, as the flip side of the coin, you have to rely on grep, sed, awk, and other tools to glue them together.

If there is a need to be multiplatform compatible, you have to be careful to avoid such strange traps as BSD and GNU Coreutils, and differences in bash versions.

Shell scripts can’t handle complex logic because of the lack of a complete data structure and a consistent API.

Use the right tools to solve a particular problem. Knowing when to use a shell and when to switch to a more general scripting language (such as Ruby/Python/Perl) is also the key to writing solid shell scripts. If your task can be done with a combination of common commands and involves simple data, shell scripts are the hammer for you. If your task has complex logic and complex data structures, you’ll need to script it in a language like Ruby/Python.