Skip to content

Instantly share code, notes, and snippets.

@butachi
Forked from mohanpedala/bash_strict_mode.md
Last active June 27, 2024 12:21
Show Gist options
  • Save butachi/5536469ed4df8d75cc39a089c9e9b16c to your computer and use it in GitHub Desktop.
Save butachi/5536469ed4df8d75cc39a089c9e9b16c to your computer and use it in GitHub Desktop.
set -e, -u, -o, -x pipefail explanation

Table of Contents

set -e, -u, -x, -o pipefail

  • The set lines
    • These lines deliberately cause your script to fail. Wait, what? Believe me, this is a good thing.

      những dòng này cố ý làm cho đoạn script của bạn bị lỗi. Đợi chút, điều gì vậy? Hãy tin ở tôi, đây là một điều tốt.

    • With these settings, certain common errors will cause the script to immediately fail, explicitly and loudly. Otherwise, you can get hidden bugs that are discovered only when they blow up in production.

      Với những thiết lập đó, những lỗi phổ biết chắc chắn sẽ làm cho đoạn script lỗi ngay lập tức, rỏ ràng và to. Ngược lại bạn có thể nhận những lỗi ẩn mà chỉ biết được khi chúng bùng phát trong môi trường sản xuất.

      set -euxo pipefail is short for:

      set -e
      set -u
      set -o pipefail
      set -x
      

set -e

  • The set -e option instructs bash to immediately exit if any command [1] has a non-zero exit status. You wouldn't want to set this for your command-line shell, but in a script it's massively helpful. In all widely used general-purpose programming languages, an unhandled runtime error

    Tuỳ chon set -e hướng dẫn bash kết thúc ngay lập tức nếu bấn cứ lệnh nào có một trạng thái thoát khác 0 (có lỗi xảy ra). Bạn không muốn thiết điều này cho dòng lệnh, nhưng trong một script nó giúp ích quan trọng. Trong tất cả các ngôn ngữ lập trình phổ biết được sử dụng rộng rãi, một lỗi thời gian chạy không được điều khiển.

  • whether that's a thrown exception in Java, or a segmentation fault in C, or a syntax error in Python - immediately halts execution of the program; subsequent lines are not executed.

    cho dù đó là một throw exception trong Java hay một segmentation fault trong C hay một syntax error trong Python - tạm dừng thực xử lý của trương trình ngay lập tức; những dòng tiếp theo không được xử lý.

    • By default, bash does not do this. This default behavior is exactly what you want if you are using bash on the command line
    • you don't want a typo to log you out! But in a script, you really want the opposite.
    • If one line in a script fails, but the last line succeeds, the whole script has a successful exit code. That makes it very easy to miss the error.
    • Again, what you want when using bash as your command-line shell and using it in scripts are at odds here. Being intolerant of errors is a lot better in scripts, and that's what set -e gives you.

set -x

  • Enables a mode of the shell where all executed commands are printed to the terminal. In your case it's clearly used for debugging, which is a typical use case for set -x : printing every command as it is executed may help you to visualize the control flow of the script if it is not functioning as expected.

    Mở một chế đọ của shell ở đó tất cả những lệnh đã thực hiện được hiển thị ở terminal. Trong trường hợp của bạn nó rõ ràng cho gỡ lỗi, chúng là một trường hợp sử dụng điển hình cho set-x : in toàn bộ lệnh đã thực hiện có thể giúp bạn hình dung luồng kiểm tra của đoạn script nếu nó không hoạt động như mong muốn.

set -u

  • Affects variables. When set, a reference to any variable you haven't previously defined - with the exceptions of $* and $@ - is an error, and causes the program to immediately exit. Languages like Python, C, Java and more all behave the same way, for all sorts of good reasons. One is so typos don't create new variables without you realizing it. For example: -> Ảnh hưởng tới các biến. Khi thiết lập, một tham chiếu tới bấn kỳ biến nào mà bạn không định nghĩa trước đó - với các ngoại lệ của $*$@ - là một lỗi, và làm cho chương trình dừng ngày lập tức. Những ngôn ngữ như Python, C, Java .. tất cả đều vận hành theo cách giống nhau, vì đủ loại lý do chính đáng. Một là lỗi chính tả không tạo ra biết mới mà bạn không phát hiện ra. Ví dụ:

    #!/bin/bash
    firstName="Aaron"
    fullName="$firstname Maxwell"
    echo "$fullName"
    
  • Take a moment and look. Do you see the error? The right-hand side of the third line says "firstname", all lowercase, instead of the camel-cased "firstName". Without the -u option, this will be a silent error. But with the -u option, the script exits on that line with an exit code of 1, printing the message "firstname: unbound variable" to stderr.

    Dành một chút thời gian và xem. Bạn có thấy lỗi không? Phía bên tay phải của dòng thứ ba có ghi "firstname", tất cả chữ thường, thay vì có một ký tự in hoa ở giữa "firstName". Không có tuỳ chọn -u, đây sẽ trở thành một lỗi thầm lặng. Nhưng với tuỳ chọn -u thì đoạn script kết thúc trên dòng đó với một mã kết thúc của 1, đưa ra thông báo "firstname: unbound variable".

  • This is what you want: have it fail explicitly and immediately, rather than create subtle bugs that may be discovered too late.

    Đây là những gì bạn muốn: nó thất bại một cách rõ ràng và tức thì, hơn là tạo ra những lỗi khó thấy có thể được phát hiện quá muộn.

set -o pipefail

  • This setting prevents errors in a pipeline from being masked. If any command in a pipeline fails, that return code will be used as the return code of the whole pipeline. By default, the pipeline's return code is that of the last command even if it succeeds. Imagine finding a sorted list of matching lines in a file:

    Thiết lập này ngăn ngừa những lỗi trong đường ống được giấu. Nếu bấn cứ lệnh nào trong một được ống lỗi, trả ra mã đó sẽ được dùng cho mã trả về của toàn bộ đường ống. Mặc định mã trả về của đường ống là mã của lệnh cuối cùng ngay cả nếu nó thành công. Hãy hình dung tìm một danh sách các dòng phù hợp được sắp xếp trong một file:

    $ grep some-string /non/existent/file | sort
    grep: /non/existent/file: No such file or directory
    % echo $?
    0
    
  • Here, grep has an exit code of 2, writes an error message to stderr, and an empty string to stdout.

  • This empty string is then passed through sort, which happily accepts it as valid input, and returns a status code of 0.

  • This is fine for a command line, but bad for a shell script: you almost certainly want the script to exit right then with a nonzero exit code... like this:

    $ set -o pipefail
    $ grep some-string /non/existent/file | sort
    grep: /non/existent/file: No such file or directory
    $ echo $?
    2
    

Setting IFS

  • The IFS variable - which stands for Internal Field Separator - controls what Bash calls word splitting. When set to a string, each character in the string is considered by Bash to separate words. This governs how bash will iterate through a sequence. For example, this script:

    Biến IFS - chúng là viết tắt của Internal Field Separator - kiểm soát những gì Bash gọi là tách từ. Khi thiết lập một chuổi, mỗi ký tự trong chuổi được xem xét bởi Bash để chia tách các từ. Điều này chỉ phối cách bash sẽ lặp lại thông qua một chuổi. Ví dụ, đoạn script này:

    #!/bin/bash
    IFS=$' '
    items="a b c"
    for x in $items; do
        echo "$x"
    done
    
    IFS=$'\n'
    for y in $items; do
        echo "$y"
    done
    ... will print out this:
    
    a
    b
    c
    a b c
    
  • In the first for loop, IFS is set to $' '. The $'...' syntax creates a string, with backslash-escaped characters replaced with special characters - like "\t" for tab and "\n" for newline.) Within the for loops, x and y are set to whatever bash considers a "word" in the original sequence.

  • For the first loop, IFS is a space, meaning that words are separated by a space character.

  • For the second loop, "words" are separated by a newline, which means bash considers the whole value of "items" as a single word. If IFS is more than one character, splitting will be done on any of those characters.

  • Got all that? The next question is, why are we setting IFS to a string consisting of a tab character and a newline? Because it gives us better behavior when iterating over a loop. By "better", I mean "much less likely to cause surprising and confusing bugs". This is apparent in working with bash arrays:

    #!/bin/bash
    names=(
    "Aaron Maxwell"
    "Wayne Gretzky"
    "David Beckham"
    )
    
    echo "With default IFS value..."
    for name in ${names[@]}; do
    echo "$name"
    done
    
    echo ""
    echo "With strict-mode IFS value..."
    IFS=$'\n\t'
    for name in ${names[@]}; do
    echo "$name"
    done
    
    
    ## Output
    With default IFS value...
    Aaron
    Maxwell
    Wayne
    Gretzky
    David
    Beckham
    
    With strict-mode IFS value...
    Aaron Maxwell
    Wayne Gretzky
    David Beckham
    

Or consider a script that takes filenames as command line arguments:

```
for arg in $@; do
    echo "doing something with file: $arg"
done
```
  • If you invoke this as myscript.sh notes todo-list 'My Resume.doc', then with the default IFS value, the third argument will be mis-parsed as two separate files - named "My" and "Resume.doc". When actually it's a file that has a space in it, named "My Resume.doc".

  • Which behavior is more generally useful? The second, of course - where we have the ability to not split on spaces. If we have an array of strings that in general contain spaces, we normally want to iterate through them item by item, and not split an individual item into several.

  • Setting IFS to $'\n\t' means that word splitting will happen only on newlines and tab characters. This very often produces useful splitting behavior.

  • By default, bash sets this to $' \n\t' - space, newline, tab - which is too eager.

Original Reference

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment