jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Bash & Shell Scripting · Part 1 — The Shell, the Shebang & Your First Script

Start scripting from zero: what a shell really is, bash vs sh, the shebang line, making a script executable, the three ways to run it, echo vs printf, and exit codes — bilingual, with exercises.

This is Part 1 of a 10-part series that takes you from “I copy-paste commands” to writing robust, production-grade shell scripts {Đây là Phần 1 của series 10 bài đưa bạn từ “copy-paste lệnh” đến viết shell script chắc chắn, mức production}. Each part is hands-on, with real examples and exercises {Mỗi phần đều thực hành, có ví dụ thật và bài tập}.

By the end of the series you will read, write, and debug shell scripts with confidence {Hết series, bạn sẽ đọc, viết, và debug shell script một cách tự tin}.


What is a shell? {Shell là gì?}

A shell is the program that reads your commands and asks the operating system to run them {Một shell là chương trình đọc lệnh của bạn rồi nhờ hệ điều hành chạy chúng}. When you open a terminal, a shell is waiting at the prompt {Khi bạn mở terminal, một shell đang chờ ở dấu nhắc}.

Bash (Bourne Again SHell) is the most common shell on Linux and the default scripting target almost everywhere {Bash là shell phổ biến nhất trên Linux và là mục tiêu scripting mặc định gần như mọi nơi}.

You type ls -l /tmp Shell (bash) parse · expand fork + exec new process Kernel runs /bin/ls exit code flows back ← ($? = 0 means success)
The shell parses your line, expands it, then fork+exec hands the real work to the kernel; an exit code comes back

There are two ways you use a shell {Có hai cách bạn dùng shell}:

  • Interactive {Tương tác}: you type one command, press Enter, see the result {gõ một lệnh, Enter, xem kết quả}.
  • Script {Kịch bản}: you put many commands in a file and run them as a batch {bỏ nhiều lệnh vào một file rồi chạy cả loạt}.

This series is about the second {Series này nói về cách thứ hai}.


bash vs sh — why it matters {bash vs sh — vì sao quan trọng}

sh is the original, minimal POSIX shell {sh là shell POSIX gốc, tối giản}. bash is a superset with far more features (arrays, [[ ]], ${var//}, etc.) {bash là tập cha với nhiều tính năng hơn hẳn}.

On many systems /bin/sh is not bash — it may be dash {Trên nhiều hệ thống /bin/sh không phải bash — có thể là dash}. A script that works with bash can break under sh {Một script chạy được với bash có thể vỡ dưới sh}. So always be explicit about which shell you target {Vậy luôn nói rõ bạn nhắm shell nào}.

Rule {Quy tắc}: if you use bash features, declare bash in the shebang (next section) and run it with bash {nếu bạn dùng tính năng bash, hãy khai báo bash ở shebang và chạy bằng bash}.


The shebang line {Dòng shebang}

The first line of a script tells the system which interpreter should run the file {Dòng đầu của script cho hệ thống biết trình thông dịch nào sẽ chạy file}. It starts with #! — called the shebang {Nó bắt đầu bằng #! — gọi là shebang}.

#!/usr/bin/env bash

Why /usr/bin/env bash instead of /bin/bash? {Vì sao dùng /usr/bin/env bash thay vì /bin/bash?} Because env finds bash on the user’s PATH, so the script works whether bash lives in /bin, /usr/bin, or Homebrew’s path {Vì env tìm bash trên PATH của người dùng, nên script chạy được dù bash nằm ở /bin, /usr/bin, hay path của Homebrew}.


Your first script {Script đầu tiên của bạn}

Create a file hello.sh {Tạo file hello.sh}:

#!/usr/bin/env bash
# hello.sh — my first script
# Lines starting with # are comments (the shebang is the one exception).

echo "Hello from a script!"
echo "Today is: $(date +%Y-%m-%d)"

Notice the # comments {Để ý các comment #}: everything after # on a line is ignored by bash {mọi thứ sau # trên một dòng đều bị bash bỏ qua}. Use them to explain why, not what {Dùng chúng để giải thích vì sao, không phải cái gì}.


Making it executable & running it {Cho phép chạy & chạy script}

A fresh .sh file is just text {File .sh mới chỉ là text}. To run it as a program you give it the execute permission {Để chạy như một chương trình, bạn cấp quyền execute}:

chmod +x hello.sh   # add the execute bit
./hello.sh          # run it (note the ./)

The ./ matters {Cái ./ quan trọng}: it means “the file in this directory” {nghĩa là “file trong thư mục này”}. Without it, the shell only searches PATH and won’t find your local file {Không có nó, shell chỉ tìm trong PATH và sẽ không thấy file của bạn}.

Three ways to run a script {Ba cách chạy script}

Command {Lệnh}What happens {Chuyện gì xảy ra}
./hello.shUses the shebang; needs execute permission {Dùng shebang; cần quyền execute}
bash hello.shForces bash; shebang & execute bit ignored {Ép bash; bỏ qua shebang & quyền execute}
source hello.sh (or . hello.sh)Runs in your current shell — variables stay after it ends {Chạy trong shell hiện tại — biến còn lại sau khi chạy xong}

Gotcha {Bẫy}: ./hello.sh and bash hello.sh run in a child shell, so any variable they set disappears when they finish {./hello.shbash hello.sh chạy trong shell con, nên biến chúng đặt sẽ biến mất khi xong}. Only source keeps them {Chỉ source giữ lại}.


echo vs printf {echo vs printf}

echo prints a line — quick and fine for simple output {echo in một dòng — nhanh và ổn cho output đơn giản}. printf is more precise and portable when you need formatting {printf chính xác và portable hơn khi cần định dạng}:

echo "Name: Alice"

printf "%-10s %s\n" "Name:" "Alice"   # control width
printf "Progress: %d%%\n" 80          # Progress: 80%

Prefer printf for anything with formatting or where echo’s flags differ across systems {Ưu tiên printf cho mọi thứ cần định dạng hoặc nơi flag của echo khác nhau giữa các hệ thống}.


Exit codes — the heartbeat of scripting {Exit code — nhịp tim của scripting}

Every command returns a number when it finishes {Mỗi lệnh trả về một con số khi kết thúc}: 0 means success, anything else means failure {0 là thành công, khác 0 là thất bại}. You read it from the special variable $? {Bạn đọc nó từ biến đặc biệt $?}:

ls /tmp
echo "$?"        # 0 — it worked

ls /nope 2>/dev/null
echo "$?"        # non-zero — it failed

This is the foundation for if, &&, ||, and error handling later in the series {Đây là nền tảng cho if, &&, ||, và xử lý lỗi ở các phần sau}. Your own script can return a code too {Script của bạn cũng có thể trả về code}:

exit 0   # success
exit 1   # generic failure

Mistakes beginners make {Lỗi người mới hay mắc}

  • ❌ Forgetting chmod +x, then confused why ./script.sh says “Permission denied” {Quên chmod +x rồi bối rối vì sao ./script.sh báo “Permission denied”}.
  • ❌ Forgetting ./ and seeing “command not found” {Quên ./ và thấy “command not found”}.
  • ❌ Editing on Windows and saving CRLF line endings — bash chokes on \r {Soạn trên Windows và lưu kiểu xuống dòng CRLF — bash nghẹn với \r}. Use LF {Dùng LF}.
  • ❌ Assuming /bin/sh is bash {Tưởng /bin/sh là bash}.

Exercises {Bài tập}

Try each before opening the solution {Thử từng bài trước khi mở lời giải}.

  1. Write a script whoami-now.sh that prints your username and the current time, then exits with code 0 {Viết script whoami-now.sh in username và giờ hiện tại, rồi thoát với code 0}.
  2. Run the same script three ways (./, bash, source) and explain the difference for a variable you set inside it {Chạy cùng script theo ba cách và giải thích khác biệt với một biến bạn đặt bên trong}.
  3. Make a command fail on purpose and print its exit code {Cố ý làm một lệnh thất bại rồi in exit code của nó}.
Solution {Lời giải}
#!/usr/bin/env bash
# whoami-now.sh
echo "User: $(whoami)"
printf "Time: %s\n" "$(date +%H:%M:%S)"
exit 0
chmod +x whoami-now.sh
./whoami-now.sh          # child shell (shebang)
bash whoami-now.sh       # child shell (forced bash)
source whoami-now.sh     # current shell — any variable set inside survives here

# Exercise 3:
grep something /no/such/file
echo "exit code: $?"     # prints a non-zero number

source runs the lines in your current shell, so a variable assigned inside the script is still set afterward {source chạy các dòng trong shell hiện tại, nên biến gán trong script vẫn còn sau đó}. ./ and bash run a child shell, so those variables vanish {./bash chạy shell con, nên các biến đó biến mất}.


Takeaway {Điều cốt lõi}

A script is just commands in a file, told which interpreter to use via the shebang, marked executable, and run with ./ {Một script chỉ là các lệnh trong file, được chỉ định interpreter qua shebang, đánh dấu executable, và chạy bằng ./}. Watch the exit code — it drives everything that follows {Theo dõi exit code — nó điều khiển mọi thứ phía sau}.