Các Lược Giảng Chuyên Sâu về Sử Dụng Văn Lệnh BASH trong Linux

Vietsciences-Làng Đậu       25/01/2006

Những bài cùng tác giả

Loạt bài " Các Lược Giảng Chuyên Sâu về Sử Dụng Văn Lệnh BASH trong Linux" của tác giả Làng Đậu giữ bản quyền 2006. Người đọc chỉ được sử dụng cho mục đích học tập hay giảng dạy cho cá nhân. Cấm mọi hình thức sao chép hay in lại nhằm mục đích mua bán hay trục lợi mà không có sự đồng ý chính thức của tác giả.  Mọi thông tin về việc phổ biến rộng rãi có tính quảng bá tài liệu này cho mục đích giáo dục xin liên lạc về vo_quang_nhan@yahoo.com  

 

Bài 5:

17 Các lệnh có sẵn (buildin) trong BASH

Sau đây là các lệnh của BASH đa số thừa hưởng từ Bourne.  Các lệnh theo chuẩn POSIX 1003.2. Một số đã được diễn giảng trong các bài trước. Phần này chỉ trình bày những gì quan trọng và chưa được đề cập. Những lệnh ít quan trọng hơn hay dể dùng xin xem bảng phụ lục của bài 1.  Các dấu ngoặc vuông [] trong các cú pháp lệnh sẽ có ý nghiã rằng các tham số trong dấu ngoặc là tuỳ chọn

Lưu ý: Đối với các lệnh sẵn có của BASH, người dùng không thể dùng man <Tên_Lệnh> để đọc cách sử dụng các lệnh này mà thay vào đó là lệnh help

17.1 : Dấu hai chấm (colon) :  không làm gì hết, thực thi dòng lệnh kế.  Trả về giá trị 0

17.2 . <TÊN_TẬP_TIN> [Tham_Số]    Lệnh dấu chấm (period) dùng để tải, đọc, và thực thi các lệnh từ trong nội dung của <TÊN_TẬP_TIN> với các tham số là [Tham_Số]. Gía trị trả về là trạng thái thoát của mệnh lệnh cuối cùng được thực thi trong tập tin. Nếu Tập tin được tải có định nghiã các hàm, thì các hàm này có thể được gọi sau đó (Xem lại phần tải hàm số bài 4 ) nếu tên là tên riêng thì trình dịch sẽ đi tìm tên này trong các đuờng tìm kiếm chưá trong $PATH.  Trường họp không tìm thấy tên tập tin này trình dịch trả về số khác 0. Như đã nói mệnh lệnh này tương đương với source ...

17.3  eval <KHỐI_LỆNH>

Đây là lệnh đánh giá. Lệnh này trả về giá trị chính là giá trị trạng thái thi hành của <KHỐI_LỆNH>.  Nếu không, lệnh không có thì nó trả về 0

Thí dụ: Lệnh sau đây sẽ chép nội dung của tất cả các dòng của tập tin source.txt thoả mãn dạng thức của  tham số đưa vào (dược áp dụng lên lệnh grep) vào trong tập tin found

#!/bin/bash
#mycommand
while [ ! "$1" = "" ]; do
    returncode=`eval grep $1 source.txt >> ./found`
    shift
    if [ ! "$returncode" = "" ]; then
        echo "string \"$1\" nout found >> found
    fi 
done

17.4  exec  [<LỆNH> [<THAM_SỐ>]]

Tương tự như C/C++, exec sẽ thay thế tiến trình của mình bằng tiến trình thực thi <LỆNH>. Nếu lệnh không có mặt thì nó phải được cung ứng qua ngỏ ống dẫn truyền.

17.5 exit <n> Thoát khỏi chương trình (hay hệ vỏ đang dùng) trả về giá trị <n> cho môi trường mẹ. Nếu <n> không dung ứng thì trả về giá trị của giá trị mà lệnh cuối dùng đã thực thi liền trước đó.

 

Bảng Mã thoát thông dụng chuẩn
 
Số mã thoát Ý nghiã Ghi chú
0 Việc thực thi văn lệnh không có lỗi Đôi khi không cần thiết để trả về các giá trị lỗi thì cũng có thể dùng giá trị 0
1 Các lỗi thông dụng được bắt ở giá trị này Các lỗi linh tinh, such as "divide by zero"
2 Dùng sai lệnh của hệ vỏ Hiếm dùng, thường mặc định trả về giá trị 1
126 Lệnh được gọi không thể thi hành Quyền sử dụng có vấn đề hay lệnh là tập tin không thể thực thi
(không có thuộc tính +x chẳng hạn)
127 "command not found" -Lệnh không tồn tạiCó thể có vấn đề trong cài đặt $PATH hay lỗi chính tả
128 Tham số không có hiệu lực (hay sai)  
128+n "fatal error"- Lỗi nghiêm trọng signal "n"exit chỉ chấp nhận giá trị trả về từ 0 đến 255
$?
trả về tối đa 137 (128 + 9)
Văn lệnh bị ngưng bởi tổ hợp phím Control-C Control-C là lỗi nghiêm trọng signal 2,
(130 = 128 + 2, xem dòng bên trên)
255* Trạng thái thoát ngoài mức exit chỉ nhận các đối số nguyên trong khoảng từ
0 - 255

17.6 export [-f][-n] [<TÊN>[=<Giá_Trị>]]

Dùng thể xuất một <TÊN> vào trong môi trường (biến toàn cục hay biến môi trường) để cho phép các tiến trình con truy cập. (Xem lại phần trước nói về lệnh này)  Các tham số:

  •  -f cho biết đó là hàm; ngoài ra, nó sẽ là biến.  Ngoài ra, <TÊN> sẽ chỉ là biến
  • -n Hủy bỏ hiệu lực của việc xuất biến này
  • Nếu không có <TÊN> trong lệnh export thì lệnh này sẽ là lệnh hiển thị danh sách các <TÊN> đã xuất

    Giá trị trả về là 0 trừ khi lệnh bị gọi sai tham số, hay <TÊN> không phải là một biến có hiệu lực, hay trong trường hợp dùng -f, mà <TÊN> không phải là một hàm

     17.7 getopts <Dãy_Tham_Số[:]>

    Đây là lệnh thoả mãn tiêu chuẩn POSIX.2 được dùng để đọc và truy nhập các giá trị của tham số truyền vào một văn lệnh.  Đây là 1 công cụ tiện lợi để tiến hành truy nhập các giá trị của tham số.  Trong cú pháp trên, mỗi tham số khi gọi sẽ  sẽ có dạng chuẩn là -<T> [Giá_Tri]. Trong đó, <T>  là tên của tham số và là một kí tự chữ hay số bất kì trong bảng kí tự ngoại trừ dấu hai chấm : và dấu hỏi ?

    Mặt khác, trong khai báo thì thì mỗi tham số T trong dãy tham số nếu được viết liền sau đó bằng dấu hai chấm :  thì tham số đó sẽ đòi hỏi phải có một giá trị đi kèm. 

    Thí dụ: nếu <Dãy_Tham_Số[:]> có dạng  am:bc  thì văn lệnh sẽ chấp nhận các tham số gọi bên ngoài của nó là:  -a, -b, -c-m <giá_trị>

    Người ta thường truy nhập các tham số của văn lệnh thông qua một vòng lặp.  Nếu văn lệnh được gọi mà không cung ứng tên tham số có trong <Dãy_Tham_Số[:]> thì tham số đó có giá trị null.

    Chỉ số của tham số được chứa trong $OPTIND (được khởi động tăng từ 1) và giá trị của tham số tương ứng chứa trong $OPTARG

    Lưu ý: Tuy nhiên, đối với các tham số có khai báo giá trị đi kèm (như trong thí dụ trên là m:  thì string đi kề trong các tham số nhập vào sẽ mặc nhiên xem là giá trị của tham số m)

    Thí dụ sau đây minh hoạ cho việc truy nhập các tham số thông qua lệnh getopts

    Parama=
    Paramb=
    Paramb_Value=
    Paramc=
    Paramc_Value=
    Param1=
    Param1_Value=
    while getopts a1:b:c name
    do
        case $name in
        a)    Parama=1"
              echo "Parama flag is $Parama"
              ;;
        b)    Paramb=1
              Paramb_Value="$OPTARG"
              echo "Paramb flag is $Paramb, and its value is $Paramb_Value"
              ;;
        c)    Paramc=1
              ;;
        1)    Param1=1
              Param1_Value="$OPTARG"
              echo "Param1 flag is $Param1, and its value is $Param1_Value"
              ;;
        *)    echo "Usage: `basename $0`: [-a] [-b value] [c] [-1 value] args\n"
              exit 2
              ;;
        esac
    done

    shift $(($OPTIND - 1))
    echo "Remaining arguments are: $*"

    17.8 hash [-r] [-dt] [<Tên_lệnh>]

    Ghi nhớ tên đầy đủ của một lệnh để sau này trình dịch sẽ tìm ra mà không cần qua biến môi trường $PATH. Các tham số thông dụng:

  • -r lệnh cho trình dịch quên đi mọi giá trị đã ghi nhớ
  • -d Yêu cầu trình dịch chỉ quên tên của <Tên_lệnh>
  • -t Hiển thị lại tên đầy đủ của <Tên_lệnh> đã ghi nhớ

    17.9 readonly [-apf] <Danh_Sách_Tên_Biến>]

    Cài các tên trong danh sách đặt tính chỉ đọc được. Cách thức này có thể dùng để định nghiã các hằng. Các tên trong danh sách có thể phân cách bởi kí tự space. Lệnh trả về 0 trừ khi một tên biến là không hợp lệ hay trong trường hợp dùng tham số -f mà tên hàm không tồn tại

    Thí dụ:

    a=1
    b=2
    readonly a b

    Các tham số thông dụng:

    • -f  Khai báo biến hằng là một hàm
    • -a Khai báo biến hằng là một mảng
    • -p hiển thị các biến hằng đã khai báo dùng lệnh readonly
    • Không có tham số : Khai báo tên biến là hằng. Nội dung của nó không thay đổi được

    17.10 test <Biểu_Thức>

    Đánh giá <Biểu_Thức>  . Lệnh này hoàn toàn tương đương với biểu thức [ <Biểu_Thức> ]  thường dùng trong các câu lệnh phân nhánh và vòng lặp, các phép toán  (Xem lại bài 3).  Các Biểu thức trong BASH có thể kết hợp nhau qua các phép toán để tạo nên biểu thức mới.  Danh sách dưới đây giảm đần thứ tự ưu tiên của các phép toán:

    1. ! <Biểu_Thức> :  Trả về true nếu <Biểu_Thức> có giá trị false
    2. ( <Biểu_Thức> ) : Trả về giá trị của <Biểu_Thức>
    3. <Biểu_Thức1> -a <Biểu_Thức2>: Trả về true nếu cả hai <Biểu_Thức1><Biểu_Thức2> đều có giá trị true
    4. <Biểu_Thức1> -o <Biểu_Thức2> : Trả về true nếu hoặc <Biểu_Thức1> là true hoặc <Biểu_Thức1> là true

    Thí dụ: lệnh sau đây sẽ kiểm tra nếu tập tin ./myfile không tồn tại thì sẽ thi hành lệnh echo "Error: ./myfile not found" và ngưng chương trình trả về giá trị 1

    test -e /myfile. || (echo "Error: ./myfile not found"; exit 1) 

    Thí dụ2: dùng tương đương với  [ ]

    X=0
    Y=1
    if test x -gt y; then
        echo "$X greater than $Y"
    else
        echo "$X not greater than $Y"
    if

    17.11 basename <Tên_Đường_Dẫn>

    Trả về tên cuối cùng trong một đường dẫn với nhiều thư mục

    Thí dụ:  Lệnh basename /etc/init.d/boot sẽ trả về giá trị boot

    17.12 dirname <Tên_Đường_Dẫn>

    Trả về phần tên của <Tên_Đường_Dẫn> đã loại trừ giá trị cuối cùng

    Thí dụ: Lệnh dirname /etc/init.d/boot sẽ trả về giá trị /etc/init.d

    17.13 trueflase:

    Lệnh true trả về trạng thái thoát thành công và không làm gì khác cả. Ngược lại false trả về trạng thái thoát thất bại cũng không có hành động gì khác.  Thường được dùng trong các vòng lặp

    Thí dụ:

    cnt=0
    while true ; do
        let "cnt +=1"
        echo "loop counter: $cnt"
        if [ $cnt -gt 9 ]; then
            echo "out of range"
            break
        fi
    done

    17.14 trap [-lp] "<KHỐI_LỆNH;>" <Danh_Sách_Tín_Hiệu>

    Khối lệnh sẽ đuợc thực thi khi văn lệnh nhận được các tín hiệu (signal) ghi trong lệnh trap. Đa số các tín hiệu bị bẫy (trap) sẽ không còn hiệu lực (tức là bị ngăn chận) như thông thường đối với các chương trình dùng lệnh này mà chỉ có <KHỐI_LỆNH;> đưọc thi hành.  <Danh_Sách_Tín_Hiệu> có thể cung cấp bằng các tên ngắn, tên đầy đủ, hay bằng các giá trị số của tín hiệu ngăn cách nhau bởi một khoảng trống (space character). Tên ngắn là tên đầy đủ nhưng bỏ đi tiền tố SIG. Lệnh này có nhiều ứng dụng như là sử dụng trap nể "cấm" hay ngăn ngưà các thao tác hay tác nhân bên ngoài (do người dùng hay chưong trình khác) có ý định làm ngưng trệ các xử lí tối quan trọng đang xãy ra trong lúc vận hành. Ngoài ra  người ta hay dùng trap trong quá trình tìm sửa lỗi.

    • -p Nếu tham số này có mặt trình bao sẽ hiển thị các lệnh trap tương ứng với các tín hiệu
    • -l Yêu cầu trình bao hiển thị danh sách các tên đầy đủ của tín hiệu và giá trị tương ứng của chúng.

    Các cú pháp:

    • trap "" <Danh_Sách_Tín_Hiệu>  : Bỏ qua các tín hiẹu trong danh dách
    • trap "<KHỐI_LỆNH>" <Danh_Sách_Tín_Hiệu> : Thi hành <KHỐI_LỆNH> nếu tín hiệu bị tìm thấy
    • trap  <Danh_Sách_Tín_Hiệu>:  Trả lại giá trị ban đầu cho các tín hiệu trong <Danh_Sách_Tín_Hiệu>
    • trap : <Danh_Sách_Tín_Hiệu> : bỏ qua tín hiệu và chuyển nó vào các tiến trình con

    Lưu ý: dấu ngoặc kép có thể thay thế bằng dấu sắc ' trong việc đặt <KHỐI_LỆNH> vào kiểu string


    Các Tín hiệu Quan trọng
     
    Tên ngắn/Tên đủ
    của Tín hiệu
    Giá trị
    bằng số
    Ý nghiã
    EXIT /SIGEXIT 0 Dùng để bắt các tín hiệu thoát khỏi script
    HUB / SIGHUP 1 logout, ngừng gọi (hang up)
    INT / SIGINT 2 Ngắt (interrupt) chương trình (bằng <Ctrl>+<c>)
    QUIT / SIGQUIT 3 Thoát khỏi lệnh đang thi hành (bằng <Ctrl>+<\>
    SIGILL 4 Chỉ thị không hợp lệ (illegal instruction)
    SIGBUS 7 Lỗi BUS
    SIGFPE 8 Ngoại lệ Điểm chấm động (Floating point exception)
    SIGKILL 9 Hủy bỏ (kill), không thể ngăn chận hay bỏ qua tín hiệu này được.
    SIGUSR1 10 Tín hiệu người dùng tự định nghiã 1
    SIGSEGV 11 Vi phạm phân đọan (segmentation violation)
    SIGUSR2 12 Tín hiệu người dùng tự định nghiã 2
    SIGPIPE 13 Ống dẫn truyền bị gãy
    TERM / SIGTERM 15 Kết thúc (terminate) - kết thúc mặc định
    SIGSTKFLT 16 Lỗi chồng xếp (stack fault)
    SIGCLD / SIGCHLD 17 Trạng thái tiến trình con thay đổi
    SIGCONT 18 Tiếp tục
    SIGSTOP 19 Stop, không thể ngăn chận tín hiệu này được
    SIGTSTP 20 Ngưng bàn phím
    SIGTTIN 21 Đọc trong nền từ tty
    SIGTTOU 22 Viết trong nền lên tty
    TSTP 24 Tín  hiệu <Ctrl>+<z>
    SIGSYS 31 Gọi từ hệ thống không đúng
    DEBUG   Tín hiệu thường dùng để gỡ lỗi, <khối_Lệnh> thi hành sau mỗi lệnh được thực thi ngoại trừ các dòng chú thích
    ERR   Khối lệnh được thi hành mỗi khi một mệnh lệnh trong văn lệnh trả về giá trị khác 0.  Tuy nhiên, tín hiệu này không được thực thi nếu các lệnh trả về giá trị khác 0 đó nằm bên trong vòng lặp, câu lệnh phân nhánh hay trong biểu thức có && || và !

    Thí dụ:
    Lệnh sau đây khi đặt vào trong một văn lệnh sẽ làm mất hiệu lực của tổ hợp phím bấm <Ctrl>+<c> để ngừng chương trình:

    trap '' 2
    # Ignore Control-C key stroke

    Tương tự như vậy, nhưng mệnh lệnh sau sẽ hiển thị dòng chữ "Control-C was pressed!" mỗi khi nó bẩy được tín hiệu số 2

    trap 'echo "Control-C was pressed!"' 2
    # Message when Control-C pressed.

    Thí dụ2: Đoạn mã sau đây sẽ chỉ cách để ngăn chận việc bấm tổ hợp <Ctrl>+<c> trong một giai đoạn cần thiết nào đó và sau đó trả lại trạng thái bình thường cho chương trình

    trap '' 2  #  Control-C, now disabled.
    command1
    command2
    command3
    ...
    trap 2     # Control-C re-enabled
    command5
    command6
    ...

    Thí dụ3:  chương trình test sau dây sẽ gọi chưong trình test2 như là một tiến trình con và sau đó 5 giây tiến trình mẹ (test) dự định hủy tiến trình con này bằng lệnh kill với tín hiệu 16 nhưng trong test2 đã có bẫy để ngăn không cho nó phép ngưng chạy bằng tín hiệu 16 này (dùng trong các trường hợp chạy chương trình con trong thời điểm nghiêm trọng không thể ngưng) . test2  sẽ tự kết thúc sau 10 giây.

    Nội dung của tập tin test
    #!/bin/bash
    #file name: test
    ./test2 &     #call test2 as a child in background
    pid=$!         #record process ID of  test2
    echo "The PID of child process: $pid"

    i=0
    while [ $i -lt 5 ]; do
        sleep 1
        echo -n ". "
        let "i +=1"
    done

    echo -e "\nAttempt to kill the child process and release signal 16 by 'kill' command:"
    kill -16 $pid

    Nội dung tập tin test2
    #!/bin/bash
    #File name test2
    i=0
    trap 'echo "some body tried to kill my process through signal 16"' 16
    echo "Verifying child process number:`pgrep test2`"
    while [ $i -lt 10 ]; do
        sleep 1 "
        let "i += 1"
       echo -n "+"
    done
    echo "I exit myseft now"

    Thí dụ4:  Truyền tín hiệu 2 (tức là tổ hợp phím bấm <Ctrl>+<c>) từ parent sang cho child xử lí

    #!/bin/bash
    #parent
    echo parent running
    trap 'echo parent exiting; exit' 0
    trap :2
    ./child &
    sleep 1000

    #!/bin/bash
    #child
    echo child started. pid is $$
    trap 'echo child got signal 2; exit' INT
    sleep 1000

    17.15  umask [-p] [-S] [<số_cơ_chế>]

    Nếu như lệnh chmod cho phép thay đổi hay xác định quyền truy cập của các tập tin thì lệnh umask ngược lại xác định giới hạn truy cập của một tập tin. Các giá trị <số_cơ_chế> bằng số trong lệnh umask chính là giá trị bù của giá trị quyền truy cập trong cơ số bát phân. Sau khi một lệnh umask được thực thi, thì tất cả các tập tin tạo ra trong khi thi hành văn lệnh (chẳng hạn qua các lệnh chuyển hướng) đều sẽ phải có giá trị truy cập theo đúng giới hạn của umask.  Ngoài ra, lệnh umask sẽ có giá trị hiệu lực kể từ khi nó được định nghiã cho dến khi có lệnh umask mới hay kết thúc văn lệnh

    Thí dụ:  Thí dụ sau sẽ tạo ra tập tin mylog.txt có giá trị quyền truy cập là 755 (tức là giá trị bù của nó khi ra lệnh umask là 022)

    #!/bin/bash
    umask 022
    tail -n 10 myfile >   mylog.txt

    Lưu ý giá trị umask thông dụng khác là 177, với giá trị này thì các tập tin được tạo ra trong văn lệnh sẽ có phép truy cập là 600 (tức là chỉ cho người tạo ra nó hay root có quyền viết/đọc tập tin.)

    Các tham số:

    • -S Nếu không có số cơ chế thì lệnh sẽ hiển thị các giá trị cài đặt hiện tại dạng kí tự
    • -p Yêu cầu hiển thị lệnh umask hiện đang có hiệu lực.
    • Nếu ra lệnh umask không có tham số đi kèm thì giá trị <số_cơ_chế> được hiển thị

    17.16 command <Lệnh> <Tham_Số_Lệnh>

    Thực thi  <Lệnh> <Tham_Số_Lệnh> bỏ qua các hàm có cùng tên.  Chỉ có những lệnh sẵn có hay các lệnh tìm thấy được trong $PATH là có thể được thi hành.  Trường hợp này thường được dùng khi trong văn lệnh có định nghiã hàm trùng tên với một lệnh nào đó của BASH hay ở bên ngoài. Lệnh này trả về giá trị 127 nếu <Lệnh> không tìm thấy hay nếu có lỗi xãy ra.

    17.17 enable [-n] [-p] [-a] <Tên>

    Dùng để hoạt hoá (enable) hay ngăn trở (diable) các lệnh có sẵn của BASH.  Việc ngăn trở của một lệnh BASH sẽ cho phép một lệnh nào khác có cùng tên được thực thi mà không cần phải ghi rõ tên đầy đủ của lệnh đó (nếu nó được lưu trong thư mục có tên trong $PATH). Bình thường thì BASH sẽ thực thi lệnh sẵn có của nó và nếu có lệnh khác cùng tên, mà không được gọi bằng tên đầy đủ, thì lệnh này sẽ không được thực thi (vì có mức ưu tiên thấp hơn) . Các tham số bao gồm: 

    • -n dùng để ngăn trở lệnh sãn có của BASH có tên là <Tên> 
    • -p Hiển thi danh mục các lệnh sẵn có của BASH đang được hoạt hoá
    • -a HIển thị danh mục các lệnh sẵn có của BASH với thông báo đính kèm lệnh nào được hoạt hoá và lệnh nào đang bị ngăn trở
    • Khi gọi lệnh mà không cung cấp bất kì <Tên> hay tham số nào thì tương đuơng với việc dùng tham số -p

    Lệnh trả về 0 trừ khi <Tên> không phải là lệnh sẵn có hay có lỗi khi gọi enable.

    17.18 help [<TÊN>] [-s <Dạng_thức>]

    HIển thị các thông tin hỗ trợ về cách dùng của các lệnh sẵn có <TÊN>.  Nếu dùng tham số -s thì lệnh này sẽ hiển thị cách dùng của các lệnh nào có tên tương thích với <Dạng_thức> và lệnh trả về 0 trừ khi không có lệnh sẵn có nào thoả mãn  <TÊN> hay <Dạng_thức>

    17. 19 printf <Định_Dạng> [<Các Đối_Số>]

    Đây là lệnh mô phỏng theo lệnh printf trong C.  Lệnh này hiển thị dòng văn bản được định dạng theo sự sắp xắp của người lập trình. <Định_Dạng> là một string bao gồm 3 loại đối tượng: 

    • Các chuỗi kí tự thông thường, sẽ được chép thẳng ra stdout
    • Các dãy kí tự thoát theo chuẩn ANSI C, được hoán chuyển theo đúng ý nghiã và đưa ra stdout
    • Các đặc tả định dạng mà mỗi đặc tả sẽ được nhập vào từ một đối số tương ứng theo thứ tự. Ngoài
      • %% sẽ cho hiển thị dấu %
      • %b Hiển thị string với việc chuyển dịch đối số tương ứng, và nếu đối số có kí tự thoát theo chuẩn ANSI sang đúng ý nghiã của nó
      • %s Hiển thị đối số tương ứng.
      • %q HIển thị đối số tương tứng trong dạng mà nó có thể được dùng như là ngỏ vào của hệ vỏ

    Thí dụ:

    PI=3.1415926
    Dec=31373
    Msg1="\tHello"
    Msg2="world"

    printf "PI with 2 decimal  = %1.2f" $PI
    printf "Pi to 5 decimal places and round off = %1.5" $PI 
    printf "Constant = \t%d\n" $DecimalConstant  # Inserts tab (\t).
    printf "%b %s \n" $Msg1 $Msg2

    17.20  read [-rs] [-a <Tên_Mảng>] [-d <Kí_tự_giới_hạn>] [-n <n>] [-t <Thời_hạn>] [-u <fd>] [<Danh_Sách_Biến>]

    Đọc một dòng từ ngỏ vào chuẩn hay từ bộ mô tả tập tin df nếu dùng tham số -u, và từ (word) đầu tiên sẽ được gán lên biến đầu tiên, từ thứ hai đuợc gán cho biến thứ hai ..., tất cả phần còn lại của dòng sẽ được gán hết lên biến cuối cùng nếu không đủ số biến trong <Danh_Sách_Biến>. Ngược lại, nếu số biến trong <Danh_Sách_Biến> nhiều hơn số từ có trong dòng đang xử lí thì các biến đi sau sẽ đưọc gán gía trị null. Các kí tự trong $IFS sẽ được dùng làm kí tự giới hạn.   Kí tự thoát, dấu nghiên về \, có thể dùng để xóa các ý nghiã đặc biệt của kí tự liền ngay sau nó và dùng để đọc dòng nối tiếp nếu đi đôi với dấu đầu dòng.  Nếu <Danh_Sách_Biến> không được cung ứng thì read sẽ gán toàn bộ giá trị của dòng đọc được cho biến REPLY.  Lệnh sẽ trả về 0 trừ khi gặp kí tự EOF, read bị hết thời hạn đợi khi đọc, hay bộ mô tả tập tin không hợp lệ khi dùng tham số -u.  Các tham số quan trọng nêu được cung ứng khi dùng lệnh này là:

    • -a <Tên_Mảng> :Các từ đọc được sẽ được gán lên các phần tử của biến kiểu có kiểu mảng <Tên_Mảng> bắt đầu từ chỉ số 0.  Tất cả các phần tử sãn có của mảng sẽ bị xoá. Tất cả các biến tham số khác sẽ bị bỏ qua khi dùng cách này.
    • -d <Kí_tự_giới_hạn>  :Dùng <Kí_tự_giới_hạn> để kết thúc cho dòng nhập vào thay vì dùng kí tự đầu dòng.
    • -n <n> : lệnh read sẽ ngừng đọc sau khi đọc được n kí tự thay vì đợi đến hết dòng nhập vào
    • -r :tham số này sẽ hủy bỏ vai trò kí tự thoát của và dấu nghiên về nay chỉ đóng vai trò bộ phận của dòng nhập vào. Đặc biệt khi dùng nó thì cặp kí tự "dấu nghiên về-dấu đầu dòng" sẽ không còn giá trị như là sự nối tiếp của dòng văn bản nữa.
    • -s : Chế độ lặng lẽ (silent mode) Nếu ngỏ vào là một đầu cuối thì các kí tự sẽ không được hiển thị lập lại
    • -t <Thời_hạn> cung ứng thời gian tối đa và trả về lỗi nếu toàn bộ dòng nhập không đọc được trong thời hạn <Thời_hạn> giây. Tham số này không có hiệu lực nếu lệnh read không nhập dữ liệu từ đầu cuối hay từ một ống dẫn truyền.
    • -u <fd> Ngỏ nhập lấy từ bộ mô tả tập tin <fd>

    Các thí dụ sau đây trích ra từ  Advanced Bash-Scripting Guide

      # A single 'read' statement can set multiple variables.
      echo -n "Enter the values of variables 'var2' and 'var3' (separated by a space or tab): "
      read var2 var3
      echo "var2 = $var2      var3 = $var3"
      # If you input only one value, the other variable(s) will remain unset (null).

      echo -n "Enter another value: "
      read           #  No variable supplied for 'read', therefore...
                     #+ Input to 'read' assigned to default variable, $REPLY.
      var="$REPLY"
      echo "\"var\" = "$var""
       

      echo "Enter a string terminated by a \\, then press <ENTER>."
      echo "Then, enter a second string, and again press <ENTER>."
      read var1     # The "\" suppresses the newline, when reading $var1.
                    #     first line \
                    #     second line

      echo "var1 = $var1"
       

      echo 1234567890 > File    # Write string to "File".
      exec 3<> File             # Open "File" and assign fd 3 to it.
      read -n 4 <&3             # Read only 4 characters.
      echo -n . >&3             # Write a decimal point there.
      exec 3>&-                 # Close fd 3.
      cat File                  # ==> 1234.67890
      exit 0

    17.21 type [-afptP] [<Danh_Sách_Tên>]

    BASH chỉ ra việc sử dụng của mỗi tên trong <Danh_Sách_Tên> là thuộc loại nào trong các loại function, builtin, file, alias, hay keyword. Các tham số có thể dùng bao gồm:

    • -p : Trả về tên của tập tin trong ổ mà sẽ được thực thi
    • -P :Buộc tìm trong $PATH cho mỗi tên trong <danh sách>
    • -a : Trả về tất cả các nơi mà chứa tập tin khả thi có cùng tên tập tin. NÓ bao gồm cả hàm và alias
    • -r : Không tìm tên hàm, và các lệnh có sẵn
    Trả về 0 nếu tên bất kì trong danh sách được tìm thấy, trả về khác không ngược lại

    17.22  ulimit [-acdflmnpstuvSH]limit]

    Cho phép điều chỉnh các tài nguyên dành cho các tiến trình khởi động bởi trình bao.  Các tham số sau có ý nghiã:

    • -S :Thay đổi và báo cáo giới hạn mềm liên hệ đến một tài nguyên; các giá trị có thể là hard, soft, và unlimited
    • -H :Thay đổi và báo cáo giới hạn cứng liên hệ đến một tài nguyên
    • -a :Báo cáo tất cả giới hạn hiện có
    • -c :Độ lớn tối đa của các tập tin cốt lõi được tạo ra
    • -d :Độ lớn tối đa của phân đoạn (segment) dữ liệu của một tiến trình
    • -f :Độ lớn tối đa của một tập tin tạo được bởi trình bao.
    • -l :Độ lớn tối đa có thể được khoá (lock) vào trong bộ nhớ
    • -n :Độ lớn tối đa số tập tin có thể mở được
    • -p :Độ lớn của bộ đệm kiểu ống dẫn, đơn vị dùng là một khối 512 bit
    • -s :Độ lớn tối đa của chồng xếp
    • -t :Độ lớn tối đa của thời gian xử lí trong CPU tính bằng giây
    • -u :Số tối đa các tiến trình mà có thể cung ứng cho một người dùng
    • -v :Độ lớn tối đa của bộ nhớa ảo có thể cung cấp cho một tiến trình

    Đơn vị sử dụng cho các báo cáo  liên quan đến tập tin là 1024 byte

    Lệnh trả về 0 trừ khi có tham số không hợp lệ hay lỗi xãy ra khi cài đặt giới hạn mới.

    Chúng ta đã xem xét qua phần nội dung chính các hỗ trợ của văn lệnh BASH. Đó là các kiến thức thuộc về lí thuyết. Trong thực tế, việc viết mã cho một chương trình nghiêm túc thường chỉ chiếm tối đa một phần ba tổng thời gian từ khi tạo ra mà nguòn đến khi thực sự dùng nó. Để thay cho phần tổng kết lý thuyết, chúng ta hãy tìm hiểu thêm vài chia sẽ trong quá trình truy sửa lỗi (debugging)

    18 Quá trình Truy sửa lỗi trong BASH   

    18.1  Một số dạng lỗi cơ bản:

    Trong các phiên bản chuẩn hiện tại của BASH không cung cấp bất kì một phưong tiện đạc biệt nào để truy sửa lỗi (debug) . Một số cách thực để tiến hành quá trình này đều do người lập trình tự tạo nên hay dùng  phần mềm hỗ trợ như phần bashdb

    Các dạng lỗi chung trong văn lệnh BASH thường bao gồm:

    18.1.1  Lỗi chính tả: Đây là dạng lỗi thông thường và cũng dể chữa nhất. Việc việt sai thường sẽ bị trình dịch bắt lỗi ngay lập tức và thông bao lỗi thường ở dạng "syntax error" hay "command not found" hay "unexpect end of file".  Một trong những nguyên do là có thể người gõ phím tạo lồi gõ hay do thói quen. Thói quen này hình thành trong việc sử dụng các ngôn ngữ khác.  Thí dụ mã VB sẽ không chiếu cố nhiều đến việc chưà khoảng trống là bao nhiêu trong biểu thức logic, hay mã Pascal không để ý đến việc viết hoa hay viết thường. Tuy nhiên, trong một số trường hợp một tên viết sai chính tả nhưng vẩn hợp lệ thì việc truy lỗi sẽ khó hơn nhiều.

    Thí dụ :   

    if  [ "$1"="a" ]; then  ....fi   #missing one space character between = sign

    WHILE [ $x = 1 ];  DO  .... DONE  #all BASH commands are in lowercase; do not capitalized!

    $x = 1     #  Wrong! To assign, use: x = 1 instead

    18.1.2 Lỗi văn phong:  Lỗi văn phong  là tất cả các dạng lỗi liên quan đến nội dung của các biến.  Việc gán hay dùng sai kiểu trên 1 loại biến đã định nghiã kiểu  cũng thuộc loại này nhưng hay thấy nhất là việc sử dụng sai hay thiếu các dấu ngoặc, các kí tự thoát của nội dung một biến string

    Thí dụ:

    echo number 8 > $1   # should be quoted.  e.g.  echo "number 8 > $1"

    cat mycodefile | grep myvar=$1   # intent to find lines content word  myvar=$1 in mycodefile;
    #iit should rewite as:  cat mycodefile | grep "myvar=\$1"'

    echo 'value of variable x is $x'  # wrong quotation; should be echo "value of x is $x"

    echo "he said: "it is OK!", but is is really not OK."   # Should change to echo -e "he said: \"it is OK!\", but it is really not OK."

    18.1.3 Lỗi thiếu, thừa hay dùng sai từ khoá:  Các lỗi dạng này có thể xãy ra trong các câu lệnh phân nhánh. 

    Thí dụ thay vì

    if
         ....
    elif 
         ...
    else
        ...
    fi 

    thì viết thành

    if
         ...
    else if
         ...
    else
         ...
    fi

    hay là viết sai thành
    if
        ....
    elif
        ....
    else
        ....
    if

    18.1.4 Dùng cùng tên đã có sẵn từ trước.

    Một khi việc này xãy ra, thường thì trình dịch sẽ có thể không phát hiện được lỗi loại này.  Ứng xử của chương trình sẽ còn tuỳ thuộc vào cách thiết lập mã và các giá trị của sự trùng lặp tên này ảnh hưởng như thế nào đến dữ liệu xử lí cũng như đến môi trường xử lí.  Nên tránh tối đa dùng lại các tên của các hàm, các biến môi trường và các lệnh chuẩn của BASH vì như vậy sẽ buộc trình dịch hoặc bắt lỗi hoặc sử dụng tên có thứ tự ưu tiên cao hơn để gọi trước (trừ khi dùng các lệnh đặc thù xử lí loại này). Việc đặt tên biến trùng với các từ khoá sẽ chắc chắn bị trình dịch phát hiện và báo lỗi.  Ngoài ra, trong chừng mực nào đó phải nhớ rõ các biến đã được xuất thành biến toàn cục cũng như các loại biến đã khai là biến địa phương thì không nên lạm dụng sai chức năng sẽ có thể gây hậu quả khôn lường. Việc đặt tên biến tránh không bị "đụng hàng" thì khá dể dàng mà ngưòi lập trình có thể có các kĩ năng riêng để làm chuyện này. 

    Ghi chú:

    • Các lệnh chuẩn thường chứa trong các thư mục tên bin hay sbin như là /bin, /sbin, /usr/bin, /usr/sbin. Có thể kiểm thêm giá trị của $PATH để tìn ra các tên lệnh khác trong hệ thống
    • Các tên lệnh sẵn có và các từ khoá trong BASH có thể dể dàng tìm thấy các taì liệu tham chiếu BASH
    • Các biến môi trường có thể được hiển thị hầu hết qua lệnh env
    • Các lệnh như hash command có thể ảnh hưởng hay thay đổi đến thứ tự ưu tiên chuẩn của các mà BASH gọi một lệnh có trùng tên

    18.1.5 Dùng sai/lầm lẫn cú pháp lệnh:  Mỗi lệnh trong Linux đều có các điều kiện về tham số và cú pháp riêng.  Thông thường, các lệnh đó sẽ phát hiện ra những lỗi thô thiển như là sai cú pháp thiếu hay thừa các tham số, tham số qúa hạn mức (out of range), ...  Việc thông báo lỗi sẽ hiển thị thông qua ngỏ ra chuẩn (thường là stderr) hay trả về các giá trị khác 0. Đối với các lệnh hỗ trợ biểu thức chính quy mở rộng (như grep, awk, sed, ...) thì người dùng nên đọc lại các siêu kí tự mà lệnh hỗ trợ; chúng có thể không hoàn toàn có cùng ý nghiã cho mỗi lệnh.. Ngoài ra, cần lưu ý có trường hợp phải dùng các tham số đặc biệt để mở hay đó một chức năng nào đó của lệnh (thí dụ muốn hỗ trợ biểu thức chính quy mở rộng trong grep thì phải dùng tham số -E). Trong trường hợp người lập trình dựng lệnh dạng script dựa trên lệnh getops thì việc gọi nó càng phải cẩn thận hơn.  

    Thí dụ sau đây sẽ cho thấy điều đó

    #!/bin/bash
    #content of mycmd
    Parama=
    Paramb=
    Parama_Value=
    while getopts a:b name
    do
        case $name in
        a)    Parama=1"
              Parama_Value="$OPTARG"
              echo "Parama flag is $Parama, and its value is $Parama_Value"
              ;;
        b)    Paramb=1
              echo "Paramb flag is $Paramb"
              ;;
        *)    echo "Usage: `basename $0`: [-a value] [-b]\n"
              exit 2
              ;;
        esac
    done

    Lệnh mycmd trên đòi hỏi tham số a phải có giá trị đi kèm. Khi gọi, nếu người lập trình "quên" không cung ứng thì hậu qủa sẽ có thể sai
    Chẳng hạn, dự định gọi lệnh mycmd với hai tham số -a-b nhưng vì thiếu giá trị cho tham số -a nên mycmd đã hiểu "lầm" rằng "-b" là giá trị của tham số -a và tham số -b có giá trị null

    #./mycmd -a -b
    #Parama flag is 1, and its value is -b

    Để khắc phục nhược điểm này thì người lập trình có thể thêm vào mycmd một vài câu lệnh kiểm nghiệm tham số nhằm phát hiện sớm hơn ngay từ bên trong mycmd mỗi khi tham số -a thiếu giá trị

    18.1.6 Viết sai cấu trúc logic của các mệnh đề:

    Việc viết sai logic của các mệnh đề hay trình bày sai nội dung của một mệnh đề sẽ gây ra các lỗi mà trình dịch không thể tìm ra.  Các lỗi này thường là do người lập trình không kiểm soát kĩ các mệnh đề logic hay dùng mệnh đề logic với quá nhiều phép toán logic nối nhau.  Cách tốt nhất để tránh lỗi loại này là đừng bao giờ dùng một mệnh đề có nhiều hơn hai phép toán logic. Vì Nhiều hơn thế, số tổ hợp logic của bảng chân trị sẽ lên đến 8 hay hơn các trường hợp khả dĩ, và như thế gây khó khăn cho việc kiểm nghiệm nếu không muốn mất thì giờ. Thay vì vậy, người ta có thể chia mụch tiêu thành nhiều phân nhánh nhỏ mỗi phân nhánh xử lý 1 hay hai trường hợp Logic là tốt nhất. 

    Một dạng hiếm thấy hơn là hiểu sai về cách trình bày của mệnh đề

    Thí dụ để xoá tất cả các tập tin trong thư mục hiện hoạt mà nội dung tên của có có kí tự khoảng trống (space). Người ta có thể làm một cách sai sót như sau:

    ls | grep ' ' | xargs -i rm {}

    Lý do của sự sai sót này là:  Trong việc hiển thị các tên tập tin có kí tự khoảng trống thì trình dịch hiển thị các tên này rất bình thường.  Nhưng một khi các tên đó được dùng làm tham số cho lệnh rm thì lệnh rm sẽ không thể biết được đó các thành phần ngăn cách nhau bở kí tự khoảbng trống là của 1 tên duy nhất  và do đó nó sẽ tìm để xoá các thành phần này thay vì tên đúng của tập tin. Để điều chỉnh có thể thư dùng sed thay vì gr

    ls | sed -n '/ /s/ /\\ /gp' | xargs -i rm {}

    18.1.7 Thiết lập sai dòng điều khiển

    Trinh dịch không có khả năng phát hiện lỗi kiểu này.  Việc này thuộc về cách thức kiến trúc một chương trình của người viết mã.  Cách tốt nhất đối với các chương trình nhỏ, người ta có thể dùng lưu đồ (flow chart) để kiểm tra các thuật toán,  việc xử lí toàn vẹn các dạng dữ liệu, hay các cách thức xử lí của chương trình.   Đối với các chương trình cở trung bình (vài trăm dòng lệnh trở lên) và lớn (từ vài ngàn dòng lệnh) thì cách tốt nhất là phải có các sơ đồ khối để phân chia chức năng và kiểm tra hoạt động của từng khối.  Tuỳ theo mức phức tạp, các khối này có thể bao gồm nhiều sơ đồ khối con nhỏ hơn nhằm lo liệu các phần riềng lẽ của khối lớn. Cuối cùng, các đơn vị nhỏ nhất có thể là các lưu đồ logic.  Viết 1 chương trình cở trung bình và cở lớn thường tốn thời gian để kiến trúc và truy sữa lỗi hơn thời gian xây dựng mã nguồn rất nhiều. Việc kiểm tra và thử nghiệm hiệu quả của một chương trình phụ thuộc vào khả năng của người (nhóm hay nhiều nhóm) tham gia. Có nhiều kĩ thuật khác nhau như là: Kiểm nghiệm kiểu hộp đen, kiểu hộp trắng, kiểm dữ liệu ngẫu nhiên, kiểm tra điều kiện biên, ...Các loại thử nghiệm này đòi hỏi người lập trình phải được trang bị hiểu biết về công nghệ phần mềm.

    18.2 Vài phương cách để truy sửa lỗi

    Việc truy sửa lỗi thường cần nhiều thời gian và công sức hơn là khi phát triển mã.  Do đó, người viết sẽ càng thành công và ít tốn thì giờ nếu càng có kinh nghiệm, cẩn thận trong lúc kiến trúc, thiết kế, xây dựng mã, và có biện pháp kiểm soát hay phản biện rõ ràng.

    Việc phải làm đầu tiên khi có một vấn đề với chương trình là: vấn đề đó phải lập lại được bởi người truy sửa. Do đó, vấn đề được mô tả càng rõ ràng chính xác và chi tiết bao nhiêu thì việc truy sửa sẽ đỡ mất thì giờ bấy nhiêu.  Trong nhiều trường hợp, vấn đề không xãy ra đối với chương trình mà là đối với việc hiểu, cách sử dụng đúng, hay việc vận hành hợp lệ của chương trình . Trong trường hợp này, cần phải bàn bạc lại với ngưòi (chủ thể) đã nêu vấn đề.

    Một khi vấn đề đã được tái lập (dupplicate) và xác định là do chương trình tạo ra thì quá trình truy lỗi bắt đầu.  Người truy sửa lúc này đã xác định được điều kiện và trạng thái để chương trình bị lỗi. Vấn đề còn lại là tìm ra căn nguyên (root cause) của lỗi, từ đó tìm đến dòng hay khối mã có lỗi, so sánh lại với kiến trúc hay thiết kế của chương trình để tìm ra các khắc phục.

    Các phần sau đây sẽ không đề cập đến quá trình truy sửa mà chỉ chú trọng đến chi tiết các kĩ năng truy sửa lỗi khi viết văn lệnh BASH

  • 18.2.1  Dựa vào thông báo của trình dịch hay của lệnh dùng:  Thông báo của trình dịch là phương tiện căn bản để phân tích lỗi.  Hầu hết các lỗi cú pháp đều bị tìm thấy qua trình dịch. Ngoài ra các thông báo trả về qua stderr cùng cho biết về tình trạng sử dụng lệnh khi mắc lỗi.  Có nhiều trường hợp lồi cú pháp sẽ không báo cáo đúng số thứ tự dòng lệnh bị lỗi.  Chẳng hạn như sự thiếu hay sai trong việc đóng và mở các dấu ngặc cho kiểu string. Thiếu các từ khoá hay kí tự (như là dấu ;; trong lệnh case) trong việc đóng các mệnh lệnh phân nhánh và vòng lặp (như là fi, done, esac ...) sẽ khiến cho trình dịch tìm đến hết tập tin và thông báo dạng "unexpected end of file".

    18.2.2 : Trình bày mã theo "khung mã": Đây là phương cách hữu hiệu để thấy được các câu lệnh lồng nhau.  Người lập trình có thể lợi dụng việc trình bày "chừa khoảng trống đầu dòng" để kiểm tra lại các khung   mã.  Các khối mã nằm càng sâu bên trong các lệnh lồng nhau thì khoảng trống đầu dòng của nó càng lớn. như vậy mõi khi khối mã bị thiếu từ khoá sẽ dể dàng kiểm tra lại chỗ nào thiếu.. với cách trình bày này người truy lỗi có thể thấy được dể dàng các phân cấp của các lệnh lồng nhau tạo điều kiện cho việc truy sửa lỗi dể hơn nhiều

    Thí dụ:

    18.2.3: Gửi thông tin về dữ liệu hiện tại ra ngoài bằng lệnh echo, printf hay bằng đổi hướng ngỏ ra chuẩn

    Một trong những phương các rất hữu hiệu để truy sửa lỗi là dùng lệnh echo,  lệnh printf,  hay đổi hướng ngỏ ra chuẩn lên một tập tin log để kiểm nghiệm lại trạng thái hay giá trị của dữ liệu và biến  (bằng mắt hay bằng các đọc lại thông tin trong  tập tin log sau đó.) Việc dùng đổi hướng ngỏ ra lên tập tin có thể tiện lợi khi lượng thông tin quá nhiều hay việc xử lý quá nhanh hay vì lí do nào đó không tiện theo dõi trực tiếp diễn biến xử lý dữ liệu qua màn hình.

    Các thông tin này nên được lắp đặt trên mỗi khối mã con hay ngay cả trên từng dòng lệnh (nếu cần) ở khu vực nghi ngờ lỗi bắt đầu xuất hiện.

    Rocky Bernstein, trong tài liệu Advanced Bash-Scripting Guide đã cung ứng một cách thức tinh tế cho phép người dùng cách thức để tạo ra một chế độ truy sửa lỗi. Các lệnh echo hiển thị các thông tin trong quá trình truy sửa chỉ hiển thị khi biến $DEBUG có giá trị on

    ### debecho (debug-echo), by Stefano Falsetto ###
      ### Will echo passed parameters only if DEBUG is set to a value. ###
      debecho () {
        if [ ! -z "$DEBUG" ]; then
           echo "$1" >&2
           #         ^^^ to stderr
        fi
      }

      DEBUG=on
      Whatever=whatnot
      debecho $Whatever   # whatnot
      #turn $DEBUG on,  anywhere there is debecho function, theree will ehoes

      DEBUG=
      Whatever=notwhat
      debecho $Whatever   # (Will not echo.)
      $DEBUG is off, it will not display information even the function debecho is present

    18.2.4: Dùng các công cụ sẵn có trong BASH như $?, trap, tee

    Trong BASH có một số lệnh và biến có thể đưọc dùng trong công việc truy sửa lỗi đó là $?, trap, và tee

    • $?  Như đã biết sẽ chứa nội dung trả về của mệnh lệnh cuối cùng được thi hành. Như vậy, nếu đọc lại giá trị này thì người lập trình có thể biết được là các mệnh lệnh có bị lỗi khi thi hành hay không.

      Thí dụ:

      mv myfile1 myfile2
      if [ ! $? = 0 ]; then
           echo "change name error"
      else
          echo "change name suceed!"
      if
       
    • tee là lệnh dùng để hiển thị nội dung của ngỏ ra chuẩn đồng thời chép các hiển thị này vào một tập tin. Với cách này người ta có thể "trích ly" các thông báo ra màn hình chuẩn (hay ngược lại vào trong một tập tin) để dùng trong truy lỗi sau này

      Thí dụ: Để đọc lại PCI ID của các thiết bị lưu trữ đồng thời chép thông tin đó vào tập tin là mylog thì có thể dùng lệnh

      lspci -n | grep class 01 | tee mylog
       
    • Như đã đề cập trên lệnh trap có thể dùng để "bắt" các tín hiệu trong lúc truy sửa lỗi.

      Thí dụ:  Đọan mã sau đây cho thấy cách hiển thị giá trị của các biến trước khi thoát khỏi chương trình:

      #!/bin/bash
      #
      trap 'echo "value when exit of  a = $a and  b = $b"' EXIT
      a=1
      b=2
      echo "value of a is $a and b is $b"
      a=0
      exit


      Thí dụ
      sau đây  cho thấy cách hiển thị giá trị của một biến tên là VAR trong mỗi dòng mà văn lệnh thi hành.  việc làm này sẽ tiện lợi nếu muốn đặc biệt theo dõi trạng thái nội dung của một biến nào đó qua từng dòng thi hành lệnh.

      VAR=value

      trap 'echo "the value of  VAR is $VAR"' DEBUG

      # rest of the script
      #!/bin/bash
      ....

    18.2.5: Ngắt các dòng xử lí bằng việc dùng dấu ghi chú #

    Trong nhiều trường hợp việc phát hiện ra dòng lệnh bị lỗi gặp khó khăn.  Người phát triển mã có thể thử dùng phương pháp loại suy bằng cách chèn các dấu ghi chú ở đầu các dòng lệnh để ngưng không cho những dòng đó được thi hành.  Có thể kết hợp cách này với các lệnh echo để việc truy sửa lỗi hữu hiệu hơn.  Ngoài ra, người ta có thể truy ra dòng lồi bằng cách "ghi chú hoá"  phân nữa số dòng mã nếu lồi không xãy ra trong số dòng không bị ghi chú thì dòng lỗi sẽ nằm trong phần mã đã bị ghi chú và cứ thế tiếp tục theo cách phân chia nhị phân cuối cùng dòng lỗi sẽ bị phát hiện.

    18.2.6: Thay đổi cung cách viết mã

    Việc tái cấu trúc lại cách viết mã cũng có thể dùng trong truy sửa lỗi nhưng việc làm này sẽ trả giá cao về thời gian.  Đôi khi lỗi không thuộc về người lập trình viết mã mà là lỗi sai sót của trình dịch hay của các lệnh hệ thống đã được dùng để thực thi trong văn lệnh. Mặc dù trường hợp như vậy hiếm khi xãy ra nhưng có khi vẩn không tránh được. Cách hay nhất là dùng một cách viết khác (hay các lệnh khác) để cho máy thực thi cùng một thao tác có chức năng tương đương hay tương tự.

    Đôi khi sự hạn chế của lệnh sẽ gây ra bế tắc, chẳng hạn, việc xử lí trực tiếp thông qua các ống dẫn truyền có khi bất khả thi hay không đủ chức năng thì người lập trình có thể nghĩ tới việc xuất các thông tin vào trong một tập tin và xử lí tập tin đó như là một ngỏ nhập của các mệnh lệnh tiếp theo đôi khi rất hiệu quả.

    Thí dụ:  Có một loại mẫu đơn form.txt sẽ được "điền tên" thay thế cho từ khoá NAME. Biến này sẽ được thay giá trị vào  tùy theo biến myName được gọi trong văn lệnh fillform và viết ra thành tập tin $myName.txt

    Content of form.txt
    dear NAME,
    As your subscription to the X magazine will be expired. ....
    ......

    Người lập trình có thể "thử" viết văn lệnh fillform như sau

    #!/bin/bash
    if [ "$1" = "" ];then
        echo "missing device name"
        exit 1
    else
        myName="$1"
    fi

    sed 's/NAME/$myName/g'  form.txt > ./$myName.txt

    Mã nêu trên sẽ không hoạt động được vì dấu kí  tự ' sẽ hạn chế tên biến $myName được chuyển đổi thành nội dung đúng. Để khắc phục có thể thay đổi cách viết thành
    sed "s/NAME/$myName/g"  form.txt > ./$myName.txt

    18.2.6 Dùng cách viết "đủ" cho các loại lệnh chẻ nhánh dòng điều khiển

    Trong các phân nhánh thường người lập trình chỉ chú ý đến dòng điều khiển mà mình quan tâm và bỏ qua không xử lí các phân nhánh còn lại.  Truy nhiên trong thực tế, nhiều khi dữ liệu có lỗi được đưa tới sẽ lọt vào đúng ngay tình huống phân nhánh mà người lập trình đã bỏ qua không xử lý.  Trong trường hợp này việc truy sử lỗi sẽ trở nên khó khăn hơn vì không có dòng xử lí liên quan đến trạng thái lỗi hiện tại và do đó chương trình có thể sẽ có những ứng xử bất ngờ.  Cách lập trình có tính cách "tự vệ" tốt là việc "bao thầu" hết các dòng chẻ nhánh mặc dù người ta không mong muốn phân nhánh đó sẽ xãy ra.

    Thí dụ sau đây dùng mã giả. Thay vì  viết dạng

    if  [ Điều kiện1 ]; then
            ....
            if [ Điều kiện 2 ]; then
                ....
            fi
            ...
    fi

    Thì điều chỉnh thành dòng chẻ đưọc xử lí "đủ" nhằm giúp cho việc truy sửa lỗi được thuận tiện hơn

    if  [ Điều kiện1 ]; then
            ....
            if [ Điều kiện 2 ]; then
                ....
            esle
                Xử lí lỗi bất ngờ hay lỗi nội bộ vi phạm điều kiện 2 (unexpectable error or internal error)
            fi
            ...
    else
            Xử lí lỗi nội bộ hay vi phạm điều kiện 1
    fi

    18.2.7 Dùng hỗ trợ khác:

    Các hỗ trợ này có thể được tung ra dạng sản phẩm bán ngoài thị trường hay tự do ở dạng giấy phép GNU. Hiện tại, có một đề án hỗ trợ việc truy sửã lỗi là bashdb.  Lệnh này có thể được download từ trang http://bashdb.sourceforge.net/

    Việc trình bày cách xử dụng thêm các công cụ bên ngoài sẽ vượt khỏi yêu cầu nội dung của loạt bài này.  Nếu người đọc có mong muốn xin hãy đọc thêm các tài liệu riêng của các hỗ trợ đó.

    19. Lập trình đa luồng (multi threading) trong BASH:

    Trong BASH người ta ít khi dùng nó cho mụch đích lập trình đa luồng thay vào đó là các ngôn ngữ mạnh hơn (như C/C++).   Tuy nhiên, BASH vẩn có khả năng để cho người lập trình viết trong kiểu kiến trúc này.  Thí dụ của Rocky Bernstein sau đây trích từ hồ sơ mở “Advanced Bash-Scripting Guide"

        # parent.sh
        # Running multiple processes on an SMP box.
        # Author: Tedman Eng

        #  This is the first of two scripts,
        #+ both of which must be present in the current working directory.




        LIMIT=$1         # Total number of process to start
        NUMPROC=4        # Number of concurrent threads (forks?)
        PROCID=1         # Starting Process ID
        echo "My PID is $$"

        function start_thread() {
                if [ $PROCID -le $LIMIT ] ; then
                        ./child.sh $PROCID&
                        let "PROCID++"
                else
                   echo "Limit reached."
                   wait
                   exit
                fi
        }

        while [ "$NUMPROC" -gt 0 ]; do
                start_thread;
                let "NUMPROC--"
        done


        while true
        do

    trap "start_thread" SIGRTMIN

        done

        exit 0



        # ======== Second script follows ========


        #!/bin/bash
        # child.sh
        # Running multiple processes on an SMP box.
        # This script is called by parent.sh.
        # Author: Tedman Eng

        temp=$RANDOM
        index=$1
        shift
        let "temp %= 5"
        let "temp += 4"
        echo "Starting $index  Time:$temp" "$@"
        sleep ${temp}
        echo "Ending $index"
        kill -s SIGRTMIN $PPID

        exit 0


        # ======================= SCRIPT AUTHOR'S NOTES ======================= #
        #  It's not completely bug free.
        #  I ran it with limit = 500 and after the first few hundred iterations,
        #+ one of the concurrent threads disappeared!
        #  Not sure if this is collisions from trap signals or something else.
        #  Once the trap is received, there's a brief moment while executing the
        #+ trap handler but before the next trap is set.  During this time, it may
        #+ be possible to miss a trap signal, thus miss spawning a child process.

        #  No doubt someone may spot the bug and will be writing
        #+ . . . in the future.

        # ===================================================================== #

        # ----------------------------------------------------------------------#

        #################################################################
        # The following is the original script written by Vernia Damiano.
        # Unfortunately, it doesn't work properly.
        #################################################################

        #!/bin/ bash

        #  Must call script with at least one integer parameter
        #+ (number of concurrent processes).
        #  All other parameters are passed through to the processes started.


        INDICE=8        # Total number of process to start
        TEMPO=5         # Maximum sleep time per process
        E_BADARGS=65    # No arg(s) passed to script.

        if [ $# -eq 0 ] # Check for at least one argument passed to script.
        then
          echo "Usage: `basename $0` number_of_processes [passed params]"
          exit $E_BADARGS
        fi

        NUMPROC=$1              # Number of concurrent process
        shift
        PARAMETRI=( "$@" )      # Parameters of each process

        function avvia() {
                 local temp
                 local index
                 temp=$RANDOM
                 index=$1
                 shift
                 let "temp %= $TEMPO"
                 let "temp += 1"
                 echo "Starting $index Time:$temp" "$@"
                 sleep ${temp}
                 echo "Ending $index"
                 kill -s SIGRTMIN $$
        }

        function parti() {
                 if [ $INDICE -gt 0 ] ; then
                      avvia $INDICE "${PARAMETRI[@]}" &
                        let "INDICE--"
                 else
                        trap : SIGRTMIN
                 fi
        }

    trap parti SIGRTMIN

        while [ "$NUMPROC" -gt 0 ]; do
                 parti;
                 let "NUMPROC--"
        done

        wait
    trap - SIGRTMIN

        exit $?

        : <<SCRIPT_AUTHOR_COMMENTS
        I had the need to run a program, with specified options, on a number of
        different files, using a SMP machine. So I thought [I'd] keep running
        a specified number of processes and start a new one each time . . . one
        of these terminates.

        The "wait" instruction does not help, since it waits for a given process
        or *all* process started in background. So I wrote [this] bash script
        that can do the job, using the " trap" instruction.
          --Vernia Damiano
        SCRIPT_AUTHOR_COMMENTS


    A17 lệnh lspci

    Lệnh lspci là một tiện ích (utility) dùng để hiển thị các thông tin về tất cả các PCI BUS của máy tính cũng như là tất cả các thiết bị phần cứng nối với chúng.  Lệnh này là không thể thiếu được cho những người phát triển các phần mềm mức độ thấp có liên quan đến BUS hay các thiết bị cùng như là các chương trình chẩn khám (diagnostics). Ngoài ra, nó còn được dùng để phát hiện các thiết bị phần cứng và để tạo các chương trình tải bộ điều vận tự động trong các phương tiện khởi động được (bootalbe media)

    Thí dụ:

    lspci

    Trong chế độ mặc định thì lệnh này sẽ hiển thị tóm lược danh sách các thiết bị.  Các tham số có thể được dùng để xuất ra các thông tin có thể cần dùng cho các chương trình khác gọi nó.

    Các tham số quan trọng bao gồm:

    • -v : thông tin được giải mã và hiển thị chi tiết thông tin này về tất cả các thiết bị.

    • -vv: Tương tự -v nhưng cung cấp thêm những thông tin có thể hữu dụng

    • -vvv:  Tương tự -vv nhưng hiển thị- mọi thứ thông tin tìm được kể cả những thông tin có vẻ không hay ho

    • -n :  Hiển thị tên nhà sản xuất (vendor) và mã số PCI của thiết bị thay vì sử dụng bảng danh mục để chuyển thành ngôn ngữ dể hiểu

    • -x :Hiển thị các thông tin nguyên dạng (dump) trong  thập lục phân của các bộ phận tiêu chuẩn của không gian cấu hình PCI

    • -xxx : Hiển thị các thông tin nguyên dạng (dump) trong  thập lục phân của toàn bộ không gian cấu hình PCI.  Trong một số thiệt bị đặc việt lệnh này có thể dây treo máy

    • -xxxx : Hiển thị các thông tin nguyên dạng (dump) trong  thập lục phân của không gian cấu hình PCI mở rộng (4096 byte) trên các BUS PCI-X 2.0 và PCI Express

    • -b : Hiển thị tất cả IRQ và các địa chỉ như là được thấy từ các bo trên PCI BUS thay vì như là được thấy bởi hạt nhân

    • -t : Trình bày dạng sơ đồ phân nhánh bao gồm mọi BUS, cầu (bridge), thiét bị và các liên kết giữa chúng

    • d [<Hãng>]:[<Thiết_bị>] Chỉ Hiển thị các ID của <Thiết_bị> thuộc vè <Hãng>

    • -i <Tập_tin> :  dùng các PCI ID thông tin trong <Tập_tin> thay vì trong /use/share/hwdata/pci.ids

    • -m : Hiển thị các thông tin nguyên dạng (dump) trong dạng để xử lí bằng các văn lệnh

    Lưu ý: Các thông tin được lspci chuyển dịch thành ngôn ngữ hiểu được đưuợc dựa trên tập tin /usr/share/hwdata/pci.ids  và /proc/bus/pci

    Bảng lớp các thiết bị PCI

    Bảng số ID của lớp các thiết bị PCI
    Mã (hex) Lớp thiết bị
    0x000000  các thiết bị PCI cũ (không tương thích với VGA)
    0x000100  các thiết bị PCI cũ (tương thích với VGA) 
    0x010000  Bộ điều khiển SCSI bus
    0x010100  Bộ điều khiển kho lưu trữ Ultra ATA
    0x010180  Bộ điều khiển IDE bus master (UDMA33?)
    0x01018a  Bộ điều khiển IDE bus master (ATA66?)
    0x0101fa  Bộ điều khiển IDE bus master (UDMA33, ALi M1533 only)
    0x010200  Bộ điều khiển FDD (viết tắt từ Floppy Disk Device: thiết bị đọc ổ mềm) 
    0x010300  Bộ điều khiển IPI bus
    0x010400  Bộ điều khiển kho lưu trữ Ultra ATA (RAID) 
    0x018000  Bộ điều khiển kho lưu trữ Ultra ATA (RAID) 
    0x020000  Bộ điều hợp ethernet
    0x020100  Bộ điều khiển token ring
    0x020200  Bộ điều khiển FDDI 
    0x020300  Bộ điều khiển ATM 
    0x020400  Bộ điều khiển ISDN
    0x028000  Bộ điều khiển mạng kiểu khác 
    0x030000  Bộ điều khiển tương thích với VGA
    0x030001  Bộ điều khiển tương thích với 8514
    0x030100  Bộ điều khiển XGA
    0x030200  Bộ điều khiển 3D
    0x038000  Bộ điều khiển hiển thị kiểu khác 
    0x040000  Thiết bị video 
    0x040100  Thiết bị audio
    0x040200  Thiết bị điện htoại máy tính
    0x048000  Thiết bị đa phưong tiện khác 
    0x050000  Bộ điều khiển  RAM  
    0x050100  Bộ điều khiển flash ROM
    0x058000  Bộ điều khiển bộ nhớ khác 
    0x060000  Cầu (bridge) CPU tới PCI
    0x060100  Cầu PCI sang ISA
    0x060200  Cầu PCI sang EISA 
    0x060300  Cầu micro channel
    0x060400  Cầu PCI sang AGP
    0x060401  Cầu PCI sang PCI (hỗ trợ giải mã hiệu chỉnh (subtractive decode))
    0x060500  Cầu PCI sang PCMCIA
    0x060600  Cầu Nu Bus 
    0x060700  Bộ điều khiển Cardbus PCMCIA
    0x060800  Cầu RACEWay (cơ chế transparent) 
    0x060801  Cầu RACEWay (cơ chế endpoint) 
    0x068000  Bộ điều khiển quản lý năng lượng
    0x070000  Bộ điều khiển liên tục tương thích với XT tổng quát
    0x070001  Bộ điều khiển liên tục tương thích với 16450  
    0x070002  Bộ điều khiển liên tục tương thích với 16550 
    0x070003  Bộ điều khiển liên tục tương thích với 16650
    0x070004  Bộ điều khiển liên tục tương thích với 16750
    0x070005  Bộ điều khiển liên tục tương thích với 16850
    0x070006  Bộ điều khiển liên tục tương thích với 16950
    0x070100  Bộ điều khiển cổng song song
    0x070101  Bộ điều khiển cổng song song hai chiều
    0x070102  Bộ điều khiển cổng song song thoả mãn chuẩn CP 1.X
    0x070103  Bộ điều khiển IEEE1284
    0x0701fe  Thiết bị mục tiêu IEEE1284
    0x070200  Bộ điều khiển liên tục đa cổng (multi port serial)
    0x070300  modem tổng quát modem 
    0x070301  modem tương thích với hayes (16450 compatible serial) 
    0x070302  modem tương thích với hayes (16550 compatible serial) 
    0x070303  modem tương thích với hayes (16650 compatible serial) 
    0x070304  modem tương thích với hayes (16750 compatible serial) 
    0x078000  PCI modem 
    0x080000  8259 PIC tổng quát
    0x080001  ISA PIC 
    0x080002  EISA PIC 
    0x080010  Bộ điều khiển ngắt I/O APIC 
    0x080020  Bộ điều khiển ngắt I/O APIC 
    0x080100  Bộ điều khiển 8237 DMA tổng quát 
    0x080101  Bộ điều khiển  ISA DMA
    0x080102  Bộ điều khiển EISA DMA
    0x080200  Bộ định thời hệ thống 8254 tổng quát 
    0x080201  Bộ định thời hệ thống ISA
    0x080202  Bộ định thời hệ thống EISA
    0x080300  Bộ điều khiển RTC tổng quát
    0x080301  Bộ điều khiển ISA RTC
    0x080400  Bộ điều khiển PCI cắm nóng tổng quát 
    0x088000  Thiết bị ngoại vi (peripheral) hệ thống khác
    0x090000  Bộ điều khiển bàn phím
    0x090100  thiết bị số hoá (viết)
    0x090200  Bộ điều khiển chuột 
    0x090300  Bộ điều khiển máy quét (scanner)
    0x090400  Bộ điều khiển cổng chơi
    0x090401  Bộ điều khiển cổng chơi kiểu cũ (legacy)
    0x098000  Thiết bị nhập khác
    0x0a0000  Thiết bị bệ gắn (docking station) tổng quát 
    0x0a8000  Thiết bị bệ gắn khác
    0x0b0000  386
    0x0b0100  486
    0x0b0200  Pentium
    0x0b1000  Alpha chip
    0x0b2000  PowerPC
    0x0b3000  MIPS
    0x0b4000  Bộ đồng xử lý (Co-processor)
    0x0c0000  Bộ điều khiển chủ IEEE1394 (firewire) 
    0x0c0010  Bộ điều khiển chủ OHCI i.LINK(IEEE 1394)
    0x0c0100  ACCESS.bus 
    0x0c0200  SSA 
    0x0c0300  Bộ điều khiển chủ mở PCI sang USB
    0x0c0310  Bộ điều khiển chủ mở PCI sang USB
    0x0c0380  Bộ điều khiển chủ USB khác
    0x0c03fe  Thiết bị USB (không phải là bộ điều khiển chủ ) 
    0x0c0400  Kênh quang (fibre channel)
    0x0c0500  Bộ điều khiển quản lý năng lượng
    0x0d0000  Bộ điều khiển tương thích với IrDA
    0x0d0100  Bộ điều khiển IR tiêu tán 
    0x0d1000  Bộ điều khiển RF controller 
    0x0d8000  Bộ điều khiển không dây khác 
    0x0e0000  Bộ điều khiển I/O thông minh  
    0x0f0000  Bộ điều khiển liên lạc vệ tinh TV
    0x0f0100  Bộ điều khiển liên lạc vệ tinh audio
    0x0f0300  Bộ điều khiển liên lạc vệ tinh voice
    0x0f0400  Bộ điều khiển liên lạc vệ tinh dữ liệu
    0x100000  Bộ điều khiển mã/ giải mã máy tính hay mạng 
    0x100100  Bộ điều khiển mã/giải mã giải trí 
    0x108000  Bộ điều khiển mã/giải mã khác
    0x110000  Bộ điều khiển DSP module DIPO
    0x118000  Bộ điều khiển DSP khác
    0xff0000  Lớp không xác định 

    Thí dụ: Bảng trên có thể gúp tìm hiẻu về PCI -ID của 1 máy cụ thể

    lspci lspci -n
    0000:00:00.0 Host bridge: ServerWorks CMIC-HE (rev 22)
    ...
    0000:00:02.0 System peripheral: Compaq Computer Corporation
    Advanced System Management Controller
    0000:00:03.0 VGA compatible controller: ATI Technologies Inc
    Rage XL (rev 27)
    0000:00:04.0 Ethernet controller: Intel Corporation 82557/8/9
    [Ethernet Pro 100] (rev 08)
    0000:00:05.0 SCSI storage controller: Adaptec AHA-3960D /
    AIC-7899A U160/m (rev 01)
    0000:00:0f.0 ISA bridge: ServerWorks CSB5 South Bridge (rev 93)
    0000:00:0f.1 IDE interface: ServerWorks CSB5 IDE Controller (rev 93)
    0000:00:0f.2 USB Controller: ServerWorks OSB4/CSB5 OHCI USB Controller (rev 05)
    0000:00:0f.3 Host bridge: ServerWorks CSB5 LPC bridge
    0000:00:10.0 Host bridge: ServerWorks CIOB30 (rev 03)
    ...
    0000:01:02.0 Network controller: Compaq Computer Corporation Compaq Netelligent 10 T PCI UTP TLAN 2.3 (rev 10)
    0000:01:1e.0 PCI Hot-plug controller: Compaq Computer Corporation PCI Hotplug Controller (rev 14)
    0000:0d:01.0 SCSI storage controller: Adaptec AHA-3960D / AIC-7899A U160/m (rev 01)
    ...
    0000:00:00.0 Class 0600: 1166:0011 (rev 22)
    ...
    0000:00:02.0 Class 0880: 0e11:a0f0

    0000:00:03.0 Class 0300: 1002:4752 (rev 27)

    0000:00:04.0 Class 0200: 8086:1229 (rev 08)

    0000:00:05.0 Class 0100: 9005:00c0 (rev 01)

    0000:00:0f.0 Class 0601: 1166:0201 (rev 93)
    0000:00:0f.1 Class 0101: 1166:0212 (rev 93)
    0000:00:0f.2 Class 0c03: 1166:0220 (rev 05)
    0000:00:0f.3 Class 0600: 1166:0225
    0000:00:10.0 Class 0600: 1166:0010 (rev 03)
    ...
    0000:01:02.0 Class 0280: 0e11:ae34 (rev 10)

    0000:01:1e.0 Class 0804: 0e11:a0f7 (rev 14)

    0000:0d:01.0 Class 0100: 9005:00c0 (rev 01)
    ...

    A19 Tìm hiểu về tập tin kiến tạo (makefile) và lệnh make

    make là một lệnh đặc biệt của Linux/UNIX thường được dùng để hỗ trợ việc chuyển dịch cập nhật hóa phần mềm từ mã nguồn (mà thường là C/C++) ra một chương trình hay phần mềm ứng dụng.  Lệnh này rất hữu dụng đối với việc kiến trúc, phân phối,  và tổ chức dịch các tập tin mã nguồn cho phù hợp.

    Hoạt động của lệnh make gắn chặt và phụ thuộc vào một tập tin đặc biệt.  Tập tin này chứa các chỉ thị và các mô tả cho make hoạt động. Nó  được gọi là tập tin kiến tạo (makefile).  Thao tác tác động của lệnh make nhằm cập nhật hoá một hay nhiều đối tượng được gọi là kiến tạo

    Vì khối lượng thông tin rất lớn, phần này chỉ trình bày những kiến thực thực tế nhất thường được dùng để tạo các tập tin kiến tạo

    A19.1 Lệnh make

    Theo văn bản hướng dẫn của GNU, make là một tiện ích có khả năng tự xác định xem những phần nào của một chương trình cần được dịch (hay dịch lại) và từ đó đưa ra các mệnh lệnh để chuyển dịch chúng. phiên bản GNU make được phát triển bởi Richard Stallman và Roland McGrath.  Sau đó, kể từ phiên bản 3.76 nó được "trông coi" bởi Paul D. Smith.

    Đa số trường hợp make được dùng cho C/C++ nhưng nó không bị giới hạn bởi ngôn ngữ lập trình mà còn có thể hỗ trợ bất kì thao tác nào trong đó có các tập tin của thao tác này cần được cập nhật tự động khi có sự thay đổi từ các đối tượng xác định trước.

    Nếu không có tham số đặc biệt, thì lệnh make sẽ tìm và đọc tập tin kiến tạo ngay tại thư mục hiện hoạt. Tên của tập tin kiến tạo này trong chế độ mặc định phải là Makefile

    Cú pháp:

    make [<tham_số>] [<Đối_Tượng>] [<Định_Nghiã_macro>]

    Các tham số cho sẵn quan trọng:

    • -d : hiển thị chi tiết các thông tin truy sửa lỗi
    • -f <Tập_Tin> : dùng <Tập_Tin> làm tập tin kiến tạo thay vì mặc định
    • -i: Bỏ qua các lỗi trả về (chữ i là viết tắt của "ignore") và cố gắng tiếp tục thi hành lệnh make nếu có thể
    • -k: Bỏ đối tược hiện tại nếu qúa trình cập nhật thất bại, nhưng vẩn tiếp tục làm việc với các đối tượng khác không liên quan (tới đối tượng bị hỏng) (k viết tắt từ chữ "keep going")
    • -n hay --dryrun : Hiển thị các lệnh nhưng không thi hành dùng trong thử nghiệm (n có nghiã là "no operation")
    • -o <Tập_Tin> :  không tái tạo <Tập_Tin> ngay cả khi nó cũ hơn các tập tin mà nó phụ thuộc vào.
    • -p: Hiển thị thêm các luật lệ và các biến khi thực thi
    • -q : (q là viết tắt của question) không thực thi chỉ kiểm tra xem tất cả đều đã cập nhật hay không trả về giá trị 0 nếu mọi thứ đều đã cập nhật. Nếu không trả lời khác 0
    • -r: Hủy bỏ hiệu lực của các quy định ngầm.  Đồng thời xóa các mặc định về phần đuôi của các quy định về phần đuôi
    • -s: Không hiển thị các dòng lệnh
    • -t: Chỉ đánh dấu các tập tin như đã cập nhật nhưng không thực sự kiến tạo chúng (viết tắt từ chữ "touch")
    • --warn-underfined-variables Hiển thị cảnh báo (warning) nếu macro được sử dụng mà không được định nghiã trước
    • -C <Thư_Mục> chuyển sang <Thư_Mục> trước khi bắt đầu các vận hành của make
    • -I <Thư_Mục> Bao gồm thêm <Thư_Mục> trong danh sách các thư mục chứa các tập tin bao gồm (included file)

    Giá trị trả về của lệnh make: make chỉ trả về một trong 3 giá trị

    • 0 : Lệnh hoàn tất không lỗi
    • 1:  Giá trị này xãy ra nếu dùng tham số '-q' và make tìm thấy một số đích chưa được cập nhật
    • 2 Nếu make tìm thấy lồi trong khi thi hành và sẽ hiển thị thông báo lỗi

    A 19.2 Tập tin kiến tạo:

    A19.2.1  Khái niệm

    Lệnh make cần và hoàn toàn phụ thuộc vào các thao tác được dẫn ra trong tập tin kiến tạo của nó. Đa số các trường hợp thì tập tin kiến tạo sẽ chỉ dẫn cho lệnh make làm thế nào để dịch và liên kết một chương trình. Nó cũng hướng dẫn cho make làm cách nào để chạy các lệnh bổ trợ (như là các thao tác để xoác một số tập tin thưà sau khi dịch xong chẳng hạn).

    Thi dụ: tập tin Makefile sau đây sẽ cho phép dịch thành một chương trình đơn giản với tên tập tin thực thi là program:

    #content of file Makefile
    program: file1.o file2.o

    file2.c:
      echo 'char *builddate="' `date` '";' >file2.c

    Khi đứng trong thư mục có chứa Makefile này và gõ lệnh make thì các thao tác sau đây sẽ tự động thực thi:

    $ make
    echo 'char *builddate="' `date` '"' >>file2.c
    cc    -c -o file2.o file2.c
    cc    -c -o file1.o file1.c
    cc file1.o file2.o -o program

    Dòng đầu là dòng bị chú -- make sẽ bỏ qua
    Dòng thứ nhì là lệnh chèn biến con trỏ *builddate với giá trị ban đầu là thông tin xuất ra từ lệnh date vào dòng cuối của tập tin file2.c . Dòng thứ ba, và thứ tư theo thứ tự là lệnh make tự gọi trình biên dịch cc để dịch các tập tin file1.c và file2.c rồi liên kết chúng với nhau thành tập tin khả thi program (trong dòng cuối)

    Lưu ý: trong thí dụ trên bắt buộc lệnh echo phải được chừa lề bằng một kí tự <tab> duy nhất

    A19.2.2 Các thành tố của một tập tin kiến tạo

    Một tập tin kiến tạo có thể bao gồm các thành tố sau đây:
    • Các quy định (rule) hay còn gọi là các hiển quy (explicit rule) : cho phép khi nào và cách nào để tạo ra hay tái tạo lại một hay nhiều tập tin.  Các tập tin này gọi là các tập tin đích (hay ngắn gọn là đích ).  Nó cũng liệt kê các tập tin mà các đích phụ thuộc vào, các tập tin đó gọi là các tiền đề. Ngoài ra, trong nó cũng có thể có các lệnh đùng để tạo ra hay cập nhật các đích.
    • Các quy định ngầm  (implicit rule) : Cho phép khi nào và cách nào để tái tạo một lớp các tập tin dựa trên tên của chúng. Nó mô tả cách thức để một đích có thể phụ phụ vào một tập tin khi mà tên của tập tin này tương tự với đích (chẳng hạn chỉ khác nhau phần tên mở rộng) cũng như là cung cấp các lệnh để tạo ra hay cập nhật các đích này.
    • Các định nghiã biến: là các dòng để khai báo biến
    • Các dòng bị chú:  Các dòng hay chuỗi kí tự bắt đầu bằng dấu #  là dòng bi chú.
    • Các lệnh thi hành:  Đây là một bộ phận của một đích. Mỗi dòng lệnh này phải được bắt đầu bằng một kí tự nhảy bước (tab), sau đó là các lệnh. Các lệnh này được make xem như là lệnh hệ thống và sẽ  gửi ra hệ vỏ để thi hành. Hệ vỏ sẽ có toàn quyền  để thực  thi các lệnh  đó.
    • Các định hướng (directive) : Là các lệnh yều cầu make thực thi một thao tác đặc biệt trong khi make đọc tập tin kiến tạo.  Các thao tác bao gồm: 
      • Đọc một tập tin kiến tạo khác
      • Quyết định (dựa vào giá trị của các biến) xem khi nào bỏ qua hay khi nào dùng đến  một bộ phận của tập tin kiến tạo
      • Xác định biến trực tiếp từ một chuỗi kí tự có nhiều dòng
        Bộ phận quan trọng nhất của các định hướng là các điều kiện (conditional) :  Cung cấp sự phân nhánh cho dòng điều khiển cho lệnh make từ các tập tin kiến tạo.
    Thí dụ để tiện theo dõi thí dụ này, các dòng dược đánh số. Tuy nhiên trong thực tế các makefile sẽ không có các con số dòng này  
    1	#This is the remarked line
    2	#file name : Myporgram
    3	#Name: ABC
    4
    5	PROG = Myprogram		# initialize variable PROG
    6	OBJS = main.obj io.obj		# list of object files (define varibale line)
    7
    8	RM = /bin/rm -f
    9
    10	# Configuration:
    11	INCLUDES= -I../include -I/usr/include
    12
    13	CC = gcc			# name of compiler 
    14	LD = gcc			# name of linker
    15
    16	# Compiler-dependent section
    17	%if $(CC) == gcc		# if compiler is bcc (conditional line)
    18		CFLAGS	= $(INCLUDES) -g -wall
    19	%elif $(CC) == icc		# else if compiler is icc
    20		LD = icc
    21		CFLAGS = $(INCLUDES)  –g
    22
    23	%else				# conditional "else"
    24	% abort Unsupported CC==$(CC)	# compiler is not supported
    25	%endif				# conditional "endif" 
    26
    17	all: $(PROG)			#top level rule: support "make all" command (see phony target)
    28
    29	$(PROG): $(OBJS)		#rule for linking (two lines: this line define dependency)
    30      	$(LD) $(OBJS) -o $(PROG)	#defind command to execute the rule
    31
    32	# rule for "main.o".
    33	main.o: main.c file1.h file2.h
    34		$(CC) $(CFLAGS) -c main.c
    35
    36	# rule for "file1.o".
    37	file1.o: file1.c file1.h
    38		$(CC) $(CFLAGS) -c file1.c
    39
    40	# rule for "file2.o".
    41	file2.o: file2.c file2.h
    42        	$(CC) $(CFLAGS) -c file2.c
    43
    44	# rule for cleaning re-compilable files: "make clean"
    45	clean:
    46		$(RM) $(PROG) $(OBJS)
    Trong thí dụ trên thì: 
    • các cặp dòng 33-34, 37-38, 41-42, và 45-46 là các quy định mà
      • Dòng đầu khai báo tên của các đối tượng nằm trong quy định.  Các tên nằm sau dấu hai chấm là các tập tin mà các đối tượng đó phụ thuộc vào (phải có để trình dịch hay lệnh make có thể hoạt động đúng)
      • Các dòng tiếp theo là dòng lệnh sẽ được gửi ra hệ vỏ để thực thi.
    • Các dòng 17,19,23,24, và 25 là các định hướng điều kiện (conditional directive)
    • Các dòng 5,6,8,11, 13,14, 18, 20, 21 ,.. là các dòng khai báo biến và/hay định nghiã giá trị của chúng
    • Các dòng bắt đầu bằng dấu # là dòng bị chú make sẽ bỏ qua

    A19.2.3 Thực thi một lệnh make:

    Việc thực thi lệnh make sẽ tùy theo các cài đặt có trong tập tin kiến tạo. Tuy nhiên, cách đơn giản nhất là thử nhập lệnh make không cần tham số.

    Ngoài các tham số cho sẵn, các tập tin kiến tạo có thể cung ứng nhiều đối số khác nhau tùy theo đích và các đích nhằm tạo ra dđ^'i số cho lệnh make này gọi là đích giả (phony target) (hay ngắn gọn là đích nếu không sợ bị nhầm lẫn). Các đích giả này sẽ được định nghiã từ bên trong các tập tin kiến tạo (xem thêm phần đích ) Dù sao,  những đối số chuẩn hay được dùng là: (xem thêm đích giả )

    • all : tạo ra tất cả các đích ở mức cao nhất mà tập tin kiến tiến có thể làm được.
    • clean : Xóa tất cả các tập tin mà thường chúng được làm ra từ việc thi hành lệnh make
    • install : Chép các tập tin khả thi vào trong các thư mục đúng chỗ của nó (nơi mà người dùng thường tìm tới các lệnh) đồng thời chép các tập phụ trợ  vào các thư mục mà  mà các chương trình tạo được ra bởi make  sẽ tìm tới chúng.
    • check hay test : Tiến hành các thử nghiệm (như là kiểm tra các điều kiện vận hành) lên chương trình mà makefile sẽ tạo ra.

    Tóm lại cú pháp gọi lênh make là:

    make [<Tham_số>] [<Các_Đích>]

    Thí dụ  Giả sử một makefile tên là "myMakefile" có hỗ trợ hai dích là clean và all.  Thì nó có thể được gọi bằng cách
    make -f myMakefile clean all

    A19.2.3  Viết một tập tin kiến tạo

    Phần này sẽ hướng dẫn sơ lược cách tạo ra một tập tin kiến tạo. Tuy nhiên, vì khối lượng thông tin khổng lồ, chúng ta chỉ tóm tắt ngắn gọn những yếu tố thiết yếu để có thể bắt đầu viết một tập tin kiến tạo. Phần đào sâu thêm sẽ đòi hỏi các bạn đọc xem thêm tài liệu tham khảo chính của GNU về make tại trang WEB hhttp://www.gnu.org/software/make/manual/make.html

    A19.2.3.1 Biến, giá trị biến và các hàm hỗ trợ

    Biến hay con gọi là macro trong lệnh make được hiểu như là một tên thay thế cho một chuỗi kí tự, chuồi này gọi là giá trị của biến. Tên của một biến có thể xuất hiện ở mọi nơi mọi thành phần của một makefile và sẽ được thay bằng giá trị của nó khi thực thi.Nội dung của biến có thể là bất kì có thể tên cuả một tập tin hay nhiều tập tin hay có thể chỉ là một phần của các tham số ...

    Quy định về tên biến:  Tên biến có thể là  chuỗi kí từ bất kì miễn là không chứa các kí tự :, #, =, hay là không thể bắt đầu hay kết thúc bằng các kí tự khoảng trắng (whitespace).  Tài liệu đặc tả về lệnh make có khuyên rằng nên đạt tên biến chỉ dùng các kí tự chữ, số  và dấu gạch dưới (underscore) đồng thời tránh dùng chữ viết hoa (các biến viết hoa thường được dùng trong các quy định ngầm -- nếu dùng trùng tên có thể tạo ra các hiệu ứng cuỡng trị (override) lên các biến của quy định ngầm)

    Để biểu thị giá trị của biến X thì có thể viết là ${X} hay $(X)

    A19.2.3.1.1 Gán gía trị cho biến:

    •  
      Thí dụ
      SOURCE = main.c main.h file1.c file1.h
      Với cách gán giá trị này thì tên biến có thể được khai triển hồi quy.  Nghiã là giá trị của biến có thể được trung chuyển thông qua các tên biến gán bởi dấu =. 
      Thi dụ: 
      one = $(two)
      two = $(three)
      three = "ABC"

      Khi đó, giá trị của biến three sẽ được chuyển dịch sang cho biến one tức là nó sẽ có giá trị là ABC
    • := 
      Thí dụ:
      OBJS = main.o file1.o  
      Cách gán giá trị này sẽ không cho phép khai triển hồi quy. Nói cách khác gán giá trị kiểu này là chỉ cho phép b>khai triển đơn giản. Các gán đơn giản này sẽ có mặt úng dụng riêng trình bày ở phần sau.
    • Dùng định hướng
      define
      <Các_dòng_kí_tự>

      endef
      Cách này cho phép gán nội dung của nhiều dòng chữ <Các_dòng_kí_tự> lên một biến. Thường được dùng để gán các chuỗi mệnh lệnh vào giá trị của một biến.
      Thí dụ:
      define two-lines
           echo file1
           echo $(file2)
      endef
      Khi thực thi (gửi ra hệ vỏ) thì sẽ tương đương với hai lệnh thi hành liền nhau (tức là echo file1; echo ${file2} )
      Lưu ý: khi dùng cách này để định nghiã thì các dấu kí tự đặc biệt như là kí tự $ các dấu ngoặc và tên các biến đều trở thành thành phần của giá trị biến được định nghiã bởi định hướng define.
    • ${<Tên_biến>:<phần_muốn_bỏ>=<phần_thay_vào>} hay $(<Tên_biến>:<phần_muốn_bỏ>=<phần_thay_vào>)  Các khai báo này sẽ cho phép dựng biến mới với nội dung tương tự như các biến đã có mà chỉ đổi phần tiếp vỹ ngữ.  Điều này khác tiện lợi cho việc khai báo biến mới với nội dung chỉ khác phần mở rộng tên các tập tin
      Thí dụ:
      file1 := a.o b.o c.o
      file2 := $(file1:.o=.c)
      sẽ cho ra biến file2 với nội dung là a.c b.c c.c

      Bị chú: Thực ra, đây là ứng dụng dạng viết ngắn của hàm patsubstr (xem phần sau)

    A19.2.3.1.2  Các hàm và phép toán hỗ trợ thay đổi nội dụng biến:

    A19.2.3.2.2.1 Phép toán

    • +=  Sẽ cho phép nối thêm vào nội dụng sẵn có của biến một giá trị trống  (space) và giá trị mới gán vào
      Thí dụ:
      OBJS = main.o file1.c
      OBJS += file2.o

      giá trị của OBJS sẽ là main.o file1.o file2.o

    A19.2.3.2.2.2 Các hàm giúp truy cập giá trị của biến:

    make hỗ trợ một số hàm nhằm thay đổi các giá trị của dòng văn bản, hay nội dung của biến.

    Lưu ý:  Các dấu phẩy,  dấu kí tự trắng và các dấu ngoặc sẽ không được có mặt trong nội dung dòng văn bản cũng như nội dung các tham số của hàm. Để vượt qua chướng ngại này thì có thể viết gián tiếp
    Thí dụ:
    comma:= ,
    empty:=
    space:= $(empty) $(empty)
    file1:= a b c
    file2:= $(subst $(space),$(comma),$(file1))
    # file2 is now `a,b,c'.

    Các hàm hỗ trợ quan trọng bao gồm:

    • subst  hàm này cho phép thay thế một bộ phận của dòng kí tự bởi bộ phận thay thế. 
      Cú Pháp:
      $(subst <phần_đục_bỏ>,<phần_thay_vào>,<Dòng_văn_bản>)
      Thí dụ:
      $(subst a,CC,the hat on the cat) sẽ đổi dòng the hat on the cat thành dòng the hCCt on the cCCt
    • patsubst sẽ tìm và thay thế các từ thoả mãn một dạng thức bằng từ mới trong dòng văn bản cho sẵn.
      Cú Pháp:
      $(patsubst <Dạng_thức>,<Từ_thay_thế>,<Dòng_văn_bản>
      Lưu ý:
      Khác với các dạng thúc trong BASH, dạng thức ở đây chỉ giới hạn trong một từ (word) -- từ trong lệnh make là một chuỗi kí tự trong dòng văn bản con được ngăn cách với từ khác bởi các kí tự trắng -- make chỉ hỗ trợ một  siêu ki tự. Đó là : kí tự % sẽ cho phép ứng hợp với mọi loại kí tự theo sau có độ dài từ 1 kí tự trở lên (cho đến kết thúc từ đang được cứu xét)  (gần tương tự như siêu kí tự * trong BASH nhưng siêu kí tự * không bị gới hạn trong biên giới của 1 từ);
      Thí dụ:
      $(patsubst %.c,%.o,x.c.c file2.c) sẽ thay các từ kết thúc bởi .c thành từ đó nhưng kết thúc là .o và hàm này cho ra giá trị x.c.o file2.o.  
      Các dạng viết tắt của hàm patsubst bao gồm
      • $(<Tên_biến>:<Dạng_thức>=<Từ_thay_thế>) sẽ tương đương với $(patsubstr <Dạng_thức>,<Từ_thay_thế>,$(<Tên_Biến>)).
      • $(<Tên_biến>:<Tiếp_vỹ_ngữ>=<Từ_thay_thế>) sẽ tương đương với $(patsubst %<Tiếp_vỹ_ngữ>,%<Từ_thay_thế>,$(<Tên_biến>)).
        Thí dụ: có biến source= main.c file1.c file2.c 
        thì có thể nhận về tên các tập tin đối tượng .c bằng cách viết 
        objs= $(source:.c=.o)
        .
        Hoàn toàn tương đương với cách viết
        objs= $(patsubst %c, %o, $(source))
    • $(strip string) Hàm này sẽ xoá tất cả các kí tự trống giữa các từ trong string và thay bằng một kí tự trống duy nhất nằm giữa các từ đó.
    • $(findstring <Giá_trị>,<Dòng_văn_bản>)  : Tìm <Giá_trị> trong <Dòng_văn_bản>
      Thí dụ
       $(findstring a,a b c) sẽ trả về giá trị a; trong khi $(findstring a,b c) sẽ trả về giá trị '' (chuỗi kí tự trống)
    • $(filter <Các_dạng_thức>, <Dòng_văn_bản>) xoá tất cả các từ không tương hợp với <Các_dạng_thức> và chỉ trả về phần còn lại tương hợp với <Các_dạng_thức> trong dòng văn bản. Ở đây các dạng thức được viết ngăn cách nhau bằng một kí tự trống
      Thí du: Trong một tập tin kiến tạo có ba dòng
      source := file1.c file2.c def.h mydat.dat mydat.s
      file1: $(sources)
          cc $(filter %.c %.h,$(source)) -o file1
      dòng lệnh thứ ba sẽ có giá trị là cc file1.c file2.c def.h -o file1
    • $(filter-out <Các_dạng thức>,<Dòng_văn_bản>) ngược lại với hàm filter, hàm này sẽ chỉ trả về phần còn lại của dòng văn bản sau khi đã loại bỏ các từ nào tương hợp với các dạng thức
    • $(sort <Dòng_văn_bản>)  Xếp thứ tự theo lối từ điển và xoá các từ trùng lặp trong <Dòng_văn_bản>
      Thí du:
      $(sort file1 file2 main file1) 

      sẽ trả về giá trị file2 file1 main
    • $(dir <Các_Tên>):  tương tự lệnh dirname trong BASH, hàm trả về tên phần thư mục chứa tên cuối cùng.
      Thí dụ:
      $(dir /home/myname/file1.c /home/myname/file2.c test)

      sẽ trả về giá trị file1.c file2.c
    • $(notdir <Các_tên>) Tương tự như lệnh basename trong BASH, hàm trả về các tên nhưng cắt bỏ phần tên thư mục chứa giá trị cuối của từ.
      Thí dụ:
      $(notdir src/file1.c /home/myname/file2.c test)
      trả về file1.c file2.c test.
    • $(suffix <Các_tên>) Chỉ trả về các tiếp vỹ ngữ của <Các_tên> mà bắt đầu bằng dấu chấm (tương ứng với phần tên mở rộng của các tập tin)
      Thí dụ:
      $(suffix file1.c /home/myName/file2.c mydata.dat)
      trả về .c .dat
    • $(basename <Các_tên>) Ngược với hàm suffix, hàm này trả về tất cả ngoại trừ phần tên mở rộng (tức là phần cuối của tên bắt đầu từ dấu chấm cuối cùng)
      Thí dụ:
      $(basename home/myname/file1.c mydat.dat)
      trả về home/myname/file1 mydat.
      Lưu ý: hàm basename này có nội dung trả về hoàn toàn khác với hàm basename trong BASH.
    • $(addsuffix <Tiếp_vỹ_ngữ>,<Các_tên>) : Thêm <Tiếp_vỹ_ngữ> vào <Các_tên>
      Thí dụ:
      $(addsuffix .c,file1 file2)
      trả về file1.c file2.c.
    • $(addprefix <Tiếp_đầu_ngữ> , <Các_tên>) Thêm <Tiếp_đầu_ngữ> vào trong <Các_tên>
      Thí du:
      $(addprefix /usr/src/,file1 file2)
      cho kết quả /usrsrc/file1 /usrsrc/file2.
    • $(wildcard <Dạng_thức>)  Dùng để gán giá trị cho biến các tên tập tin nhưng dùng kí tự phỏng định
      Thí dụ: 
      OBJS= $(wildcard *.c)  
      cho kết quả là tất cả các tên có phần mở rộng là .c

    A19.2.3.2.2.3 Các hàm thông dụng khác có tác động lên biến bao gồm:

    • $(if condition,then-part[,else-part]) : make sẽ đánh giá condition bằng cách cắt bỏ các kí tự trống ở trước và sau condition rồi sau đó khai triển (expand) tham số này (thường là tên biến). Nếu condition có giá trị false (tức đó là một string độ dài bằng 0) thì phần else-part(nếu có) sẽ được khai triển; ngược lại phần then-part sẽ được khai triển.
      Thí dụ:
      INCLUDE_FLG += -Iinclude $(if ($BUILDSRC, -Iinclude2, -I$(src)/include)
    • $(foreach var,list,text) :  khai triển đối số list, sau đó lần lượt gán lên var các giá trị của mỗi từ có trong list theo thứ tự và thực thi text.
      Thí dụ1:
      dirs := a b c d
      files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

      Sẽ cho kết quả nội dung của biến file lần lượt là  $(wildcard a/*),  $(wildcard b/*), $(wildcard c/*), $(wildcard d/*). Mệnh đề for trên sẽ tương đương với việc gán files := $(wildcard a/* b/* c/* d/*)
      Thí dụ2:
      LINK_HEDERS := $(for header, $(LINK_HEADERS), include/asm/$(header))
    • $(call <Tên_biến>,<Tham_Số1>,<Tham_Số2>,...) : Khi thực thi (expand) hàm này, make sẽ tạm thời gán các giá trị <Tham_Số1>, <Tham_Số2>, ... vào thành giá trị của các biến tham số mặc định $1, $2, ... Sau đó nó sê tiến hành khai triển giá trị của biến <Tên_biến>.  Do đó, nếu <Tên_biến> có nội dung chứa đựng các giá trị biến $1, $2 ,.. thì nó sẽ làm ảnh hưởng tới nội dung tương ứng của <Tên_biến>  
      Thí dụ:
      reverse = $(2) $(1)
      file1 = $(call reverse,a,b) 
       
      kết quả là file1 = b a
    • $(shell <Lệnh> Hàm này sẽ tiến hành thực thi <Lệnh> thông qua hệ vỏ và hiển thị ngỏ ra sau khi thư thi sẽ được trả về
      Thí du:
      contents := $(shell cat file1)

    A19.2.3.2.3 Cưỡng trị (override) và kháng cưỡng trị của biến khi gọi lệnh make
    Giả sử có biến X đã được gán giá trị trong tập kiến tạo là Makefile.  Nay người dùng muốn thay đổi giá trị mặc định này của X mà không muốn điều chỉnh nội dung tập tin kiến tạo thì thay vì gọi make <Các_Tham_Số> có thể dùng cách gọi dạng
    make X='<giá_trị>' <Các_Tham_Số>  để thay đổi nó. Biến X trong thao tác này được gọi là bị cưỡng trị. Trong trường hợp này, tham số X sẽ được cài đặt giá trị ban đầu mới (<giá_trị>) thay vì giá trị khởi động sẵn có trong Makefile.

    Make chỉ hỗ trợ một số tham biến được phép cưỡng trị hầu hết là để dùng với các tham số của các lệnh trình dịch (hường thấy nhất là biến CFLAGS ).

    Sau đây là danh sách các biến (mặc định) quan trọng có thể bị cưỡng quyền:

    • ASFLAGS : Biến thêm vào để dùng với hợp ngữ (assembler) (khi gọi với tập tin .s hay .S).
    • CFLAGS : Biến thêm vào để dùng với trình dịch C.
    • CXXFLAGS : Biến thêm vào để dùng với trình dịch C++ .
    • COFLAGS : Biến thêm vào để dùng với trình RCS co program.
    • CPPFLAGS : Biến thêm vào để dùng với chương trình tiền xử lý C và các chương trình dùng tới nó (như là trình dịch C và Fortran).
    • FFLAGS Biến thêm vào để dùng với trình dịch Fortran.
    • GFLAGS  Biến thêm vào để dùng với trình SCCS .
    • LDFLAGS Biến thêm vào để dùng với trình dịch mà nó hỗ trợ gọi bộ liên kết (linker) `ld'.
    • LFLAGS  Biến thêm vào để dùng với trình Lex.
    • YFLAGS Biến thêm vào để dùng với trình Yacc.
    • PFLAGS Biến thêm vào để dùng với trình dịch Pascal.
    • RFLAGS Biến thêm vào để dùng với trình dịch Fortran lên các chương trình Ratfor.
    • LINTFLAGS Biến thêm vào để dùng với trình lint.

    Thí dụ trong một Makefile ta đã định nghiã CFLAGS = -g để dùng trong lệnh cc -c $(CFLAGS) file1.c . Nhưng trong một số trường hợp người dùng có thể đổi giá trị  tham số này thành -g -O bằng cách gọi lệnh make dạng:
    make CFLAGS='-g -O'
    hay là
    make CFLAG:='-g -O'  Nếu chỉ muốn dùng phép gán đơn giản

    Gần như ngược lại trong nhiều trường hợp rất đặc biệt, ở một số dòng cụ thể nào đó của Makefile, người ta không cho phép lệnh make áp dụng thao tác cưỡng trị lên những dòng mà có biến có giá trị đặc biệt đó thì có thể dùng thao tác kháng lại tính cưỡng trị thông qua định hướng cưỡng trị (overide directive) theo cú pháp:

    override <Tên_biến> = <Giá_trị>

    Việc thiết kế định hướng override là nhằm cho người viết makefile có thể điều chỉnh các đối số mà người dùng gửi vào.

    A19.2.3.3 Quy định: Đây là thành phần quan trọng nhất của một Makefie.  Như tên gọi các quy định sẽ quy định cách thức cũng như sự phụ thuộc  và cách tạo ra các đích tức là các tập tin, các chương trình mà người lập trình muốn có.

    Cú pháp chung của một quy định là:

    <Các_Đích>: <Các_Tiền_Đề>
           
    <Khối_Lệnh>

    Trong đó:

    • <Các_Đích> (targets) trường hợp tổng quát là tên của các tập tin ngăn cách nhau bởi kí tự trống. Trường hợp đặc biệt <Các_Đích> không phải là tên tập tin sẽ là các đích giả (phony target)
    • <Các_Tiền_Đề> là tên của các tập tin mà các tập tin đích phụ thuộc vào. Mọi lệnh cần thiết để tạo nên một tiền đề sẽ phải được thực thi hoàn toàn trước khi bắt đầu thi hành các lệnh để tạo ra một tập tin đích tương ứng
    • <Khối_Lệnh> luôn luôn bắt đầu với một kí tự TAB (kí tự nhây bước). Các lệnh ngăn cách nhau bởi dấu chấm phẩy (;) sẽ được gởi ra trình bao để thi hành

    Lưu ý: 
    Bắt đầu của dòng <Khối_Lệnh> phải là một kí tự nhảy bước (<TAB>) và chỉ một mà thôi
    Danh sách tên các tập tin trong
    <Các_Đích> và trong <Các_Tiền_Đề> đều có thể dùng kí tự phỏng định để miêu tả cùng một lúc nhiều tập tin vào trong danh sách. Tuy nhiên, điều này không  đúng khi dùng trong định nghiã biến (Thí dụ: định nghiã  OBJS = *.o  sẽ không được hiểu là các tập tin có phần nối dài là .o mà để đạt điều này phải dùng hàm hỗ trợ cho nội dung biến tức là phải khai báo thành
    OBJS = $(wildcard *.o)

    Các dòng quá dài có thể cắt ra thành nhiều dòng con ngắn hơn bằng cách chèn dấu nghiêng về \ chỗ bị cắt ở cuối mỗi dòng con đó

    Mỗi quy định sẽ cung cấp cho make hai thông tin:  Khi nào các đích đã bị quá hạn (out-of-date) và làm thế nào để cập nhật chúng khi cần.  Một đích được xem là quá hạn khi nó cũ hơn bất kì một trong các tập tin tiền đề hoặc khi nó không có mặt. Khối lệnh là phương thức để tái tạo các đích đã quá hạn và đây là các lệnh mà hệ vỏ sẽ thi hành. Tất cả các lệnh để kiến tạo

    Thí dụ:
    edit : main.o kbd.o command.o display.o \
            insert.o search.o files.o utils.o
           cc -o edit main.o kbd.o command.o display.o \
           insert.o search.o files.o utils.o

    A19.2.3.2.1: Quy định ngầm: Để tiện lợi và giảm công sức cho việc viết mã, một số lớn các quy định được cung cấp sẵn.  Các quy định đó sẽ được make tìm kiếm và áp dụng tự động một khi make tìm thấy một tên tập tin trong makefile (như là tên của một đích hay tên của một tiền đề) mà lại không có một quy định nào để chỉ ra cách tạo nên tập tin đó.

    Thí dụ: Một makefiel có nội dung:
    CFLAGS = -c -O
    LDFLAGS = -g

    myprog: myprog.o file1.o
            cc -o myprogram file1.o myprog.o $(CFLAGS) $(LDFLAGS)

    Vì trong tập tin có đề cập tới file1.o nhưng lại không chỉ ra làm thế nào để có file1.o nên make sẽ tự động tìm trong các quy định ngầm của nó mà áp dụng (Trường hợp này dưạ trên mệnh lệnh là cc nên make sẽ tự tìm file1.c và dịch ra thành file1.o)

    A19.2.3.2.2 Các quy định ngầm thông dụng:  

    • Dịch các chương trình C :  <Tên>.o sẽ được tự động làm ra từ <Tên>.c qua mệnh lệnh dạng:
      $(CC) -c $(CPPFLAGS) $(CFLAGS)'
    • Dịch các chương trình C++ : <Tên>.o sẽ được tự động làm ra từ <Tên>.cc,  <Tên>.cpp hay <Tên>.C qua mệnh lệnh dạng:
      $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
    • Dịch các chương trình Pascal :  <Tên>.o sẽ được tự động làm ra từ <Tên>.p qua mệnh lệnh dạng:
      $(PC) -c $(PFLAGS)
    • Dịch các chương trình Fortran và Raftor : Tuỳ theo sự có mặt của tên mở rộng, <Tên>.o sẽ được tự động làm ra:
      • .f  Qua mệnh lệnh dạng: $(FC) -c $(FFLAGS)
      • .F Qua mệnh lệnh dạng:  $(FC) -c $(FFLAGS) $(CPPFLAGS)
      • .r Qua mệnh lệnh dạng:  $(FC) -c $(FFLAGS) $(RFLAGS)
    • Dịch các chương trình hợp ngữ và tiền xử lý :  Tuỳ theo sự có mặt của tên mở rộng, <Tên>.o sẽ được tự động làm ra:
      • .s từ trình dịch as qua mệnh lệnh dạng $(AS) $(ASFLAGS)
      • .S từ các bộ tiền xử lý C, cpp qua mệnh lệnh dạng $(CPP) $(CPPFLAGS)
    • Liên kết (link) một tập tin đối tượng: <Tên> được tự động từ <Tên>.o bằng cách chạy linker (bộ liên kết) qua trình dịch C bằng mệnh lệnh
      $(CC) $(LDFLAGS) <Tên>.o $(LOADLIBES) $(LDLIBS)
    • Xem thêm chi tiết hỗ trợ trong các tài liệu tra cứu của make (liệt kê trong phần tài liệu tham khảo)

    Trong đó có các biến mà make đã định nghiã sẵn.  Khi cần người viết makefile có thể định nghiã lại. Một số biến bao gồm

    • AS Trình dịch cho các tập tin hợp ngữ mặc định là as.
    • CC Trình dịch cho chương trình C mặc định là cc.
    • CXX Trình dịch cho chương trình C++ mặc định là g++.
    • CPP Chương trình để chạy bộ tiền xử lý C với các kết quả cho ra ngỏ ra chuẩn mặc định là $(CC) -E.
    • FC Trình dịch cho chương trình Fortran và Ratfor mặc định là `f77'.
    • PC Trình dịch cho chương trình Pascal mặc định là pc
    • ASFLAGS Tham số cho hợp ngữ  (khi gọi lên các tập tin.s hay .S).
    • CFLAGS Tham số cho trình dịch C.
    • CXXFLAGS Tham số cho trình dịch C++.
    • CPPFLAGS Tham số cho bộ tiền xử lí C (C preprocessor) và các chương trình nào dùng nó (trình dịch C và Fortran).
    • FFLAGS Tham số cho trình dịch Fortran.
    • LDFLAGS Tham số cho  trình dịch khi nó gọi bộ liên kết (linker) ld.
    • PFLAGS Tham số cho trình dịch Pascal.
    • RFLAGS Tham số cho trình dịch Fortran cho các chương trình Ratfor.

    A19.2.3.3 Đích giả : Đich giả là đích mà thực sự tên của nó không phải là tên của tập tin. Đích giả tạo ra nhằm chỉ để thực thi những thao tác riêng biệt mà người viết makefile tạo ra.  

    Thi dụ1:
    clean:
            rm *.o *.tmp

    Trong trường hợp này, nếu không tồn tại tập tin với tên clean, thì lệnh rm của quy định clean sẽ thi hành mỗi lần nhập make clean.  Tuy nhiên, nếu như tồn tại một tập tin với tên clean thì do tập tin này không bị ảnh hưởng bởi các tiền đề (không có ìệt kê tên của các tập tin tiền đề của quy định clean) nên lệnh gọi bởi quy định này (rm) sẽ không được thực thi vì make cho rằng không có gì mới để cập nhật.

    Để tránh trường hợp này, có thể dùng đích giả đặc biệt cung cấp sẵn bởi make là .PHONY.  Với tên này thì quy định sẽ được thực thi bất kể tập tin clean có tồn tại hay không:

    Thí dụ2: Thay vì dùng tên clean hãy dùng .PHONY làm tên đích

    .PHONY:
    clean
            clean: rm *.o *.tmp

    Một ứng dụng khác của đích giả được trình bay trong các thí dụ sau:

    Thí dụ3: thực thi các tập tin kiến tạo trong các thư mục con thông qua một quy định bằng cách viết:

    SUBDIRS = dir1 dir2 dir3
    subdirs:
            for dir in $(SUBDIRS); do \
                $(MAKE) -C $$dir; \
            done

    Cách viết trên có điểm yếu là nếu xãy ra một lỗi bất kì trong quá trình thi hành các makefile con trong vòng lặp thì make (mẹ) vẩn tiếp tục chạy mà không dừng lại ở vị trí lỗi.   Đồng thời, sự hỗ trợ việc chạy song song các quy định của make cũng không xãy ra (vì tất cả gom chung lại trong một quy định) nên ảnh hưỏng đến hiệu năng của việc thực thi make. Cách viết khắc phục hai yếu điểm này là tận dụng đích giả .PHONY

    SUBDIRS = dir1 dir2 dir3
    .PHONY: subdirs $(SUBDIRS)
    subdirs: $(SUBDIRS)
    $(SUBDIRS):
    &        $(MAKE) -C $@
    dir1: dir3

    Theo cách viết này thì dir1 sẽ không thể được chạy cho đến khi dir3 được hoàn tất

    Lưu ý: Đích giả có thể "phụ thuộc" vào nhiều tiền đề. Mỗi tiền đề đến lượt nó lại là một đích có lệnh thực thi riêng..

    Thí dụ3:
     all : prog1 prog2 prog3
    .PHONY : all

    prog1 : prog1.o utils.o
            cc -o prog1 prog1.o utils.o
    prog2 : prog2.o
            cc -o prog2 prog2.o
    prog3 : prog3.o sort.o utils.o
            cc -o prog3 prog3.o sort.o utils.o

    với cách viết này thì người dùng có thể gọi để tạo những đích khác nhau chẳng hạn như make all để tạo ra prog1 prog2prog3 . Nhưng cũng có thể gọi make prog2 chỉ để tạo prog2 mà thôi.

    Ngoài ra, make còn cung cấp các tên đích có chức năng khác ít phổ biến như .PRECIOUS, .INTERMEDIATE, .SECONDARY,  .IGNORE, .EXPORT_ALL_VARIABLES , ... (xem thêm taì liệu tra cứu của GNU)

    A19.2.3.4 Các biến thông dụng

    A19.2.3.4.1 VPATH:  biến VPATH là một biến toàn cục của make dùng để chỉ ra danh sách các thư mục mà make sẽ tìm kiếm.  Biến này sẽ bao gồm tên các thư mục có chứa các tập tin tiền đề mà chúng không có mặt trong thư mục hiện hoạt; đồng thời nó cũng có thể là tên các thư mục chứa các tập tin đích của các quy định. Một khi tên một tập tin dù là đích hay là tiền đề mà không có mặt trong thư mục hiện hoạt thì make sẽ tìm kiếm nó thông qua biến VPATH. Tương tự như biến PATH trong BASH, VPATH nối tên các thư mục bởi các dấu :

    Thí dụ:  VPATH = /src:../include

    Xin xem thêm các biến khác như của make trong các tài liệu tra cứu chính thức của GNU về make

    AA19.2.3.4.2  Biến tự động (Automatic Variable)

    Việc tạo ra các biến tự dộng nhằm thoả mản nhu cầu tự động hoá các thao tác và giảm thiểu mã cần viết cho makefile.  Thí dụ người lập trình có bộ mã nguồn C là A.c B.c, C.c., ... Việc thường thấy trong quá trình dịch là tạo ra các tệp đối tượng tương ứng A.o, B.o, C.o, ... Rồi sau cùng, có thể liên kết nó thành chương trình thực dụng. Các biến này sẽ giúp tự động hoán đổi tên (trong trường hợp này là việc thay đổi các tên mở rộng cuả các tập tin)

    Lưu ý:  Các biến tự động chỉ có giá trị và hữu hiệu nội trong các câu lệnh. Nếu sử dụng các biến này bên ngoài các câu lệnh thì chúng chỉ có giá trị rỗng.

    Danh sách các biến tự động phổ biến bao gồm

    • $@ :  Có nội dung là tên tập tin đích của quy định tương ứng. Trong trường hợp quy định là quy định dạng thức có nhiều đích thì $@ sẽ là tên của đích nào làm nguyên nhân cho sự thực thi mệnh lệnh của quy định
    • $< :  Có nội dung là tên của tập tin tiền lệ đầu tiên.  Nếu đích có lệnh thực thi của nó nằm trong một quy định ngầm, thì biến này sẽ là tập tin tiền lệ đầu tiên được thêm vào bởi quy định ngầm
    • $? :  Tên của tất cả các tệp tiền lệ nào mới hơn tập tin đích và là danh sách được viết ngăn cách các tên với nhau bởi các kí tự trắng. (Đây là tên bao gồm cả tên thư mục chứa của các tệp tiền lệ)
    • $^  :  Tên của tất cả các tệp tiền lệ và là danh sách được viết ngăn cách các tên với nhau bởi các kí tự trắng.  Tuy nhiên, nó s bỏ đi những tên nào trùng lặp
    • $+ :  Tương tự như $^, nhưng ở đây các tên trùng lặp được lặp lại theo đúng thứ tự mà chúng được liệt kê ttrong makefile.  Điều này hữu dụng chủ yếu trong các lệnh liên kết, nó có ý nghiã khi mà nó lặp lại tên của các thư viện trong một thứ tự riêng nào đó.
    • $*Phần gốc (stem) mà một quy định ngầm tương thích. Nếu đích là dir/a.file1.b và dạng thức đích là a.%.b thì phần gốc là dir/file1. Phần gốc này hữu dụng để tạo ra các tên của những tệp liên hệ. Trong một quy định dạng thức tĩnh, phần gốc là bộ phân của tên tập tin mà nó tương thích với % trong đích đạng thức.
    • $(@D) :  Phần tên của một thư mục của một đích mà đã bị xoá đi phần bắt đầu từ kí tự nghiên tới (slash) Thí dụ nếu giá trị của $@dir/file1.o thì $(@D)dir. Khi mà $@ không có dấu nghiên tới thì $(@D) là dấu chấm .
    • $(@F) :  Phần tên ngắn của tên của một đích. Nếu giá trị của $@dir/file1.c thì $(@F) có giá trị là file1.c

    Lưu ý:  $(@F) tương đương với $(notdir $@)

    Thí dụ:( trích từ http://www.opussoftware.com/tutorial/TutMakefile.htm )
    1    CC=g++
    2    CFLAGS=-c -Wall
    3    LDFLAGS=
    4    SOURCES=main.cpp hello.cpp factorial.cpp
    5    OBJECTS=$(SOURCES:.cpp=.o)
    6    EXECUTABLE=hello
    7
    8    all: $(SOURCES) $(EXECUTABLE)
    9    $(EXECUTABLE): $(OBJECTS)
    10            $(CC) $(LDFLAGS) $(OBJECTS) -o $@
    11
    12  .cpp.o:
    13            $(CC) $(CFLAGS) $< -o $@

    Dòng 10 tương đương với:   $(CC) $(LDFLAGS) $(OBJECTS) -o $(EXECUTABLE)
    Dòng 12: quy định áp dụng cho các tệp có cùng tên mở rộng .cpp.o lệnh thực thi là (dòng 13):  $(CC) $(CFLAGS) $< -o $@  (biên dịch từ .cpp sang .o cho mọi tập tin có tên mở rộng là .cpp)

    Thí dụ2:
    VPATH = /src:../headers
    file1.o : file1.c defs.h hack.h
            cc -c $(CFLAGS) $< -o $@  
            (dòng lệnh này tương đương với
            cc -c $(CFLAGS) file1.c -o file1.o )

    A19.2.3.5 Các định hướng thông dụng của make

    A19.2.3.5.1 includeđịnh hướng này có tác dụng lệnh cho make ngưng đọc tập tin kiến tạo hiện tại và chuyển sang đọc các tập tin kiến tạo khác trước khi tiếp tục các dòng kế tiếp. Định hướng này dùng để tổ chức một chương trình lớn bao gồm nhiều chương trình nhỏ trong nhiều thư mục và mỗi chương trình nhỏ này có các makefile riêng (thí dụ như trường hợp makefile của mã nguồn nhạt nhân)

    Cú pháp:
    include
    <Tên_các_tập_tin>

    A19.2.3.5.2 export : Dùng để xuất các biến vào trong các tập tin kiến tạo con. Khi gặp lệnh này make sẽ thêm vào tên và giá trị của các biến được xuất ra vào trong môi trường của tập tin kiến tạo con. Đặc biệt biến MAKFLAGS luôn luôn được xuất dùng chung cho các tập tin kiến tạo con.

    Cú pháp:

    • export <Tên_Biến>
    • export <Tên_Biến> = <Giá_trị>
    • export <Tên_Biến> := <Giá_trị>

    Đặc biệt nếu chỉ viết: export  không có <Tên_Biến> đi kèm có nghiã là yêu cầu make xuất tất cả các biến hiện có trong tập tin kiến tạo hiện tại, ngoại trừ các tên biến đã được ngăn chận bởi lệnh unexport

    A19.2.3.5.3 unexport: là định nhướng để ngăn không cho một tên biến được xuất ra cho các tập tin kiến tạo con.

    Cú Pháp:
    unexport <Tên_Biến>

    A19.2.3.5.4 define : Dùng để định nghiã các dòng lệnh mà chúng đại diện bởi một tên biến và biến này được định nghiã riêng biệt.

    Cú pháp:
    define <Tên>
    <Các_dòng_lệnh>
    endef

    Thí dụ: Định nghiã một khối lệnh hay được sử dụng:
    define run-yacc
    yacc $(firstword $^)
    mv y.tab.c $@
    endef

    Khi cần dùng trong một quy định sẽ gọi chẳng hạn như :
    file1.c : file1.y
            $(run-yacc)

    A19.2.3.5.5 override : Việc thiết kế định hướng override là nhằm cho người viết makefile có thể điều chỉnh các đối số mà người dùng gửi vào. Có thể áp dụng trong trường hợp ngăn người dùng gửi/gán các giá trị không mong muốn lên các biến khi gọi lệnh make. (Xem thêm phần A19.2.3.2.3) Với định hướng này thì tùy theo cách gán giá trị ta có các cú pháp sau:

    • override <Tên_biến> = <Giá_trị>
    • override <Tên_biến> := <Giá_trị>
    • override <Tên_biến> += <Giá_trị>

    A19.2.3.5.6 Các định hướng điều kiện (conditional) : Là định hướng khiến cho một phần của tập tin makefile được thực thi hay bỏ qua tùy theo giá trị của các biến.

    Trong thí dụ của phần A19.2.2 thì các dòng 17,19,23,24, và 25 mô tả các định hướng (dạng lệnh if)

    Thí dụ:
    ifeq ($(CC),gcc)
            libs=$(libs_for_gcc)
    else
            libs=$(normal_libs)
    endif

    Cú pháp:

    •  <Định_Hướng_Điều_Kiện>
                  
      <Các_dòng_mã nguồn_nếu_đúng>
         endif
       
    •  <Định_Hướng_Điều_Kiện>
                 
      <Các_dòng_mã nguồn_nếu_đúng>
         else   
                
      <Các_dòng_mã nguồn_nếu_sai>
         endif
       
    •  <Định_Hướng_Điều_Kiện1>
                 
      <Các_dòng_mã nguồn_nếu_đúng1>
         else
      <Định_Hướng_Điều_Kiện2>
                 
      <Các_dòng_mã nguồn_nếu_đúng2>
         else
               
      <Các_dòng_mã nguồn_nếu_sai>
         endif


      Trong đó <Định_Hướng_Điều_Kiệ> thuôc về một trong các dạng
      • ifeq <String1> <String2>  (hay còn viết dạng ifeq(<String1>,<String2>) ) Nếu <String1> bằng <String2>
      • ifneq <String1> <String2> . Nếu <String1> không bằng <String2>
      • ifdef <Tên_Biến>    Nếu <Tên_Biến> đã được dịnh nghiã
      • ifndef <Tên_Biến>  Nếu <Tên_Biến> chưa được định nghiã
        Lưu ý: <String2> ở đây nếu không có mặt sẽ xem như là so sánh với chỗi kí tự trống (Thí dụ ifdef ($file2, ) )

    A19.2.4 Các quy định đặc biệt: Là các quy định mà thành phần của nó có thể không có mặt hoặc có nhiều hơn 1 thành phần của cùng loại. Bao gồm

    A19.2.4.1 Quy định thiếu lệnh và tiền đề :  Nếu một quy định  A chỉ có tên mà không có mặt của dòng lệnh và các tiền đề thì make sẽ tiến hành thực thi các quy định chứa quy định A như là tiền đề một cách vô điều kiện.

    Thí dụ:
    clean: FORCE
            rm -f *.o; touch *
    FORCE:

    Ở đây FORCE thuộc vào trường hợp này và do đó khi gọi make clean thì dòng lệnh rm -f *.o; touch * sễ được thực thi mà không cần kiểm tra lại sự cập nhật của các tập tin liên đới.  Quy định viết cách này tưong đương với đích giả .PHONY

    A19.2.4.2 Quy định với đích là tập tin trống:  Đây lcũng là một cách trình bày khác tương tự với đích giả dùng để thực thi một số lệnh cài sẵn khi được yêu cầu. Khác với đích thông thường hay đích giả, tập tin đích ở đây là hiện hữu nhưng nội dung của nó không cần được quan tâm và thường là tập tin có độ dài bằng 0.  Mục đích của tập tin này là để lưu giữ thời điểm cuối cùng mà nó được truy cập để dùng trong mụch đích kiểm tra sự phụ thuộc hay sự cập nhật hoá.  Dòng lệnh của quy định này bao gồm lệnh touch (để cài lại dấu thời gian (time stamp) )

    Thí dụ: Lệnh sau đây dùng để hiển thị tập tin mã nguồn nào có sự thay đổi (cập nhật) mỗi khi gọi lệnh make update
    update: file1.c file2.c
            cat $?
            touch update

    A19.2.4.3 Quy định đa đích : Cách viết này tương đương với viết nhiều quy định và những quy định này có chung tiền đề và câu lệnh.  Hơn thế nữa, có thể vận dụng các biến tự độngcác hàm để tạo thêm sự linh hoạt.

    Thí dụ: Quy định sau đây sẽ cho phép trích các dòng có chứa chữ long của tập tin mytext.dat viết vào trong tập tin longprint khi gọi make longprint; và tương ứng trích các dòng của mytext.dat có chứa chữ short vào trong tập tin shortprint khi nhập lệnh make shortprint
    longprint, shortprint : mytext.dat
            grep -$(subst print,,$@)  mytext.dat > $@

    A19.2.4.4 Quy định dạng thức (pattern rule) : Để mở rộng nội hàm hay thu gọn cách viết cho tên (tập tin) đích và tên tiền đề của quy định người ta đặt thêm một siêu kí tự là %. Kí tự nàu tương thích với dãy kí tự bất kì khác trống (hãy so sánh với siêu kí tư * và, +  trong dạng thức của lệnh grep).  Thêm vào đó, nếu kí tự % có mặt trong tiền đề thì nó phải tương ứng với kí tự % (nếu có trong đích)

    Thí dụ khi viết
    %o : %c
            $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

    Thì quy định trên sẽ chỉ ra cách tạo tập tin <tên>.o từ tập tin cùng tên <Tên>.c thông qua dòng lệnh

    A19.2.4.5 Quy định dạng thức tĩnh (static patern rule) : Cách viết này đưọc tạo ra nhằm đặc biệt xử lý trường hợp nhiều đích và cấu trúc các tên tiền đề cho mỗi đích dựa vào tên của đích tương ứng. Phương tiện này tổng quát hơn là các quy định nhiều đích thông thường vì nó không đòi hỏi các đích phải có cùng tiền đề. Các tên tiền đề chỉ phải tương tự nhau (mà không cần trùng lặp)

    Cú pháp:
    <Các_đích> : <Dạng_thức_đích> : <Các_dạng_thức_tiền_đề>
           
    <Dòng_lệnh>

    Trong đó,

    • <Các_đích> là danh sách các đích mà quy định sẽ áp dụng lên.  Tên các tập đích này có thể dùng kí tự phỏng định.
    • <Dạng_thức_đích><Các_dạng_thức_tiền_đề> chỉ ra cách thức để xác định tiền đề cho mỗi đích.  Mỗi đích sẽ tương hợp với <Dạng_thức_đích> để trích ly ra một phần của tên đích, phần tương hợp này được gọi là gốc (stem).  Gốc sẽ  được thay vào trong mỗi tên  <Các_dạng_thức_tiền_đề> để tạo ra các tên tiền đề. (Mỗi tên cho một dạng thức)
    • Mỗi dạng thức thường chỉ chứa một kí tự %. Khi <Dạng_thức_đích> tương hợp với một đích, thì % có thể ứng với bộ phận nào đó của tên đích này.
    • Các tên tiền đề cho từng đích được tạo (tìm) ra bằng cách thay thế gốc lên kí tự % trong mỗi dạng thức tiền đề.  Trường hợp một dạng thức tiền đề không chứa kí tự % thì khi đó, tiền đề này không đổi (tên) cho mọi đích.

    Thí dụ:
    objects = file1.o file2.o
    all: $(objects)
    $(objects): %.o: %.c
                 $(CC) -c $(CFLAGS) $< -o $@
    Đối với đích thứ nhất file1.o thì gốc là file1 tương ứng với kí tự % trong %.o. Do đó, % sẽ bị thay bởi file1 trong dạng thức %.c và câu lệnh sẽ tương đương với 
    $(CC) -c $(CFLAGS) file1.c -o file1.o
    Tương tự, cho đích file2.o.

    A19.2.5 Lưu ý về tập tin tiền đề: Trong các makefile, nhiều quy định khi các tập tin đối tượng (obj) phụ thuộc vào một số tập header.  (thí dụ main.c sử dụng def.h thông qua lệnh #include). Do đó trong tập tin kiến tạo sẽ viết thành
    main.o: def.h.h
    Quy định này nhằm cập nhật lại main.o mỗi lần def.h thay đổi. Trong các chưong trình lớn các quy định như vậy sẽ đòi hỏi sự cẩn thận khi thêm hay bớt các câu lệnh #include.  Để tránh tình trạng khó khăn này, các trình dịch C thường hỗ trợ tham só -M. Chẳng hạn như lệnh:
            cc -M main.c
    sẽ tạo thành :
            main.o : def.h
    mà không cần người viết makefile pải viết quy định đó ra. Điều này sẽ giảm thiểu các khó khăn về tập tiền đề.

    A19.3 Các Thí dụ:

    Thí dụ1  Một trong những tập tin kiến tạo phức tạp nhất là tập tin Makefile của hạt nhân Linux. Như một bài tập hãy tìm tất cả các đích giả của tập tin này (HD: Dùng lệnh grep để trích ly ra các dòng PHONY từ Makefile) Từ đó biết được các đối số nào có thể dùng được để gọi từ make (các đối số thông dụng là mrproper, clean, dep, modules, modules_install, all)
    Sử dụng để dịch một hạt nhân cho Linux:  Đối với một mã nguồn hạt nhân mới có thể dùng cách sau đây để cấu hình và cài đặt lên máy theo thứ tự:;:

    • make mrpoper : để xoá sạch và điều chỉnh lại các giá trị cấu hình mặc định
    • make menuconfig : để tái cấu hình (thêm bớt chức năng cho hạt nhân)
    • make dep : tạo các mối liên hệ tương thuộc (nhiều hạt nhân nay đã bỏ qua không dùng đích này)
    • make bzImgaes (hay đôi khi là make compressed) đẻ tạo một tập tin ảnh của hạt nhân
    • make modules: để tạo ra toàn bộ các tệp thư viện và các bộ điều vận cần thiết cho hạt nhân
    • make modules_install: để cài đặt lên các thư mục hạt nhân vưà được tạo ra vào đúng chỗ của nó

    Sau đó có thể chép tập tin bzImage vào trong thư mục khởi động (thường là /boot) và dùng lệnh mkinitrd để tạo ra  ổ RAM khởi động (initial RAM disk) và cuối cùng tạo ra một trình đơn con trong tập tin cấu hình của bộ tâi khởi động (boot loader) thường là LiLo hay Grub.

    Thí dụ2: Như một sự khởi đầu thí dụ sau đây trích từ trang http://mrbook.org/tutorials/make/ của Hector Urtubia

    Chương trình tính 5! bao gồm tổng cộng 4 tập tin mã nguồn (well! hơi nhiều cho 1 chương trình bé như thế.  Như đây là thí dụ minh họa)

    Nội dụng main.cpp:
    #include <iostream.h>
    #include "functions.h"

    int main(){
    print_hello();
    cout << endl;
    cout << "The factorial of 5 is " << factorial(5) << endl;
    return 0;
    }


    Nội dung function.h:
    void print_hello();
    int factorial(int n);

    Nội dung factorial.cpp
    #include "functions.h"
    int factorial(int n){
        if(n!=1){
        return(n * factorial(n-1));
        }
        else return 1;
    }


    Nội dung hello.cpp
    #include <iostream.h>
    #include "functions.h"
    void print_hello(){
        cout << "Hello World!";
    }

    Và nội dung Makefile:
    CC=g++
    CFLAGS=-c -Wall
    LDFLAGS=
    SOURCES=main.cpp hello.cpp factorial.cpp
    OBJECTS=$(SOURCES:.cpp=.o)
    EXECUTABLE=hello

    all: $(SOURCES) $(EXECUTABLE)

    $(EXECUTABLE): $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) -o $@

    .cpp.o:
    $(CC) $(CFLAGS) $< -o $@

    Thí dụ3: Makefile kết cấu với hai chương trình và ba tập nguồn. Thí dụ này lấy từ http://www.cs.toronto.edu/~reid/csc209/02f/examples/make/make.html của Karen Reid năm 2002. Thí dụ có điều chỉnh vài chi tiết so với thí dụ nguyên thủy có thể được điều chỉnh để xử lý nhiều chưong trình trong Linux

    Nội dung của Makefile
    CFLAGS = -g -Wall

    CC = gcc
    LIBS = -lm
    INCLUDES =
    OBJS = a.o b.o c.o
    SRCS = a.c b.c c.c prog1.c prog2.c
    HDRS = abc.h

    all: prog1 prog2

    # The variable $@ has the value of the target. In this case $@ = psort
    prog1: prog1.o ${OBJS}
    ${CC} ${CFLAGS} ${INCLUDES} -o $@ prog1.o ${OBJS} ${LIBS}

    prog2: prog2.o ${OBJS}
    ${CC} ${CFLAGS} -o $@ prog2.o ${OBJS} ${LIBS}

    .c.o:
    ${CC} ${CFLAGS} ${INCLUDES} -c $<

    depend:
    makedepend ${SRCS}

    clean:
    rm *.o core *~

    tar:
    tar cf code.tar Makefile *.c *.h testfile1

    print:
    more Makefile $(HDRS) $(SRCS)

    Nội dung abc.h:
    void a();
    void b();
    void c();

    Nội dung a.c:
    #include <stdio.h>

    void
    a() {
    printf("function a\n");
    }

    Nội dung b.c:
    #include <stdio.h>

    void
    a() {
    printf("function b\n");
    }

    Nội dung c.c:
    #include <stdio.h>

    void
    a() {
    printf("function c\n");
    }

    Nội dung prog1.c
    #include <stdio.h>
    #include <stdlib.h>
    #include "abc.h"

    int main()
    {
    printf("Program 1 \n");
    a();
    b();
    c();
    exit(0);
    }

    Nội dung prog2.c
    #include <stdio.h>
    #include <stdlib.h>
    #include "abc.h"

    int main()
    {
    printf("Program 2 \n");
    a();
    b();
    c();
    exit(0);
    }

     

    Bài kì tới:  awk, rpm -- Kết luận --Các Tham chiếu

    © http://vietsciences.free.fr , http://vietsciences.org  và http://vietsciences2.free.fr  Làng Đậu

  •