Golang đại pháp – 1.2 Xử lý tham số trong command-line
Go
41
golang
39
White

nhaancs viết ngày 01/01/2018

Bài viết được đăng tải đầu tiên tại: http://nhaancs.com/golang-dai-phap-1-1-hello-world/

golang dai phap

Hầu hết các chương trình xử lý dữ liệu input và xuất ra output. Nhưng input lấy từ đâu ra? Một số chương trình tạo ra dữ liệu của riêng nó, nhưng đa số input thường đến từ một nguồn bên ngoài như: file, network connection, output của chương trình khác, các tham số command-line, ... Trong các ví dụ sắp tới chúng ta sẽ tìm hiểu về các nguồn trên, bắt đầu với tham số command-line.

Go cung cấp cho chúng ta package os, là một package khá tuyệt vời để làm việc với hệ điều hành trên mọi nền tảng. Biến Args là một phần trong package os lưu trữ các tham số command-line. Bên ngoài package os, Args được truy xuất như sau: os.Args.

Để dễ hình dung, ví dụ các bạn chạy một chương trình Go như sau: $ ./go_program param1 param2 thì param1param2 là hai tham số mà bạn truyền vào cho chương trình xử lý và bạn dùng os.Args để bắt các tham số này trong chương trình của mình.

Biến os.Args là một slice của string. Slice là một khái niệm cơ bản của Go mà chúng ta sẽ tìm hiểu chi tiết trong các chương sau. Còn bây giờ, các bạn có thể xem slice như là một mảng s các phần tử mà số phần tử trong mảng đó có thể thay đổi được. Mỗi phần tử có thể được truy xuất bằng cú pháp s[i], và có thể được chia nhỏ thành các tập hợp con: s[m:n]. len(s) trả về số phần tử của slice. Giống như nhiều ngôn ngữ khác, Go dùng half-open interval để đơn giản hoá logic, tức là tập hợp phần tử có index đầu tiên nhưng không bao gồm phần tử có index sau. Ví dụ: slice s[m:n] có 0 <= m <= n <= len(s) và chứa n-m phần tử.

os.Args[0] chứa tên chương trình. Ví dụ: go_program. Các phần tử còn lại các các tham số truyền vào khi chương trình chạy. Như bên trên thì s[m:n] trả về một slice bao gồm các phần tử từ index m dến n-1, nếu m hay n bị bỏ qua thì thì chúng lần lượt nhận giá trị mặc định là 0len(s). Vì vậy, ví dụ của chúng ta cần các tham số trong slice os.Args[1:len(os.Args)] hay os.Args[1:].

Bây giờ xắn tay áo lên code thôi nào. Chúng ta sẽ viết một chương trình in ra tất cả các tham số command-line trên một dòng. Nếu bạn import nhiều hơn một package thì chỉ cần dùng một câu lệnh import như bên dưới:

Ví dụ 1: file echo1.go

// Echo1 in tất cả các tham số command-line 
package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}

Build chương trình:

$ go build echo1.go

Chạy chương trình với tham số command-line:

$ ./echo1 a b c

Kết quả hiển thị là:

a b c

Phần comment ở đầu file bắt đầu với //, tất cả các ký tự phía sau // cho đến hết dòng là phần ghi chú của lập trình viên và sẽ bị trình biên dịch bỏ qua. Theo qui ước thì trước phần khai báo mỗi package cần có phần comment để mô tả package đó. Đối với package main phần comment mô tả ngắn gọn về toàn bộ chương trình.

Từ khoá var khai báo 2 biến ssep kiểu string. Khi khai báo biến chúng ta có thể khởi tạo giá trị mặc định cho biến, nếu không thì Go sẽ tự động gán cho biến đó giá trị zero ứng với kiểu dữ liệu của nó (0 cho kiểu số, chuỗi rỗng cho kiểu string). Trong ví dụ này, ssep được khởi tão gía trị mặc định là chuỗi rỗng.

Toán tử + khi dùng cho string sẽ nối các chuỗi lại với nhau. Vì vậy: sep + os.Args[i] nối hai chuỗi lại với nhau.

s += sep + os.Args[i] tương đương với s = s + sep + os.Args[i], tương tự với các toán tử khác như -, /, *, ...

Đầu tiên sep có giá trị rỗng, sau vòng lặp thứ hai, sep có giá trị bằng một khoảng trắng. Sau cùng chúng ta co một chuỗi các tham số cách nhau bằng một khoảng trắng.

Biến i được khai báo ngay sau từ khoá for, khởi tạo bằng 1. Ký hiệu := được dùng để khai báo một hay nhiều biến một cách ngắn gọn, biến khai báo theo cách này có kiểu tương ứng với kiểu của giá trị khởi tạo.

i++ tương đương i =+ 1 hay i = i + 1. Tương tự với i--. Bạn cần lưu ý là i++ hay i-- là một câu lệnh chứ không phải biểu thức như các ngôn ngữ khác, vì vậy câu lệnh j = i++là không hợp lệ. Trong Go toán tử tăng giảm chỉ đứng sau toán hạng, vì vậy ++i hay --i cũng không hợp lệ.

Vòng lặp for mình thấy khá là thú vị vì đây là vòng lặp duy nhất của Go, có thể được biến tấu thành nhiều dạng khác nhau, bao gồm ba thành phần:

for initialization; condition; post {
    // zero or more statements 
}

Bạn có thể thấy là không có cặp dấu ngoặc tròn bao quanh ba thành phần trong câu lệnh for. Cặp ngoặc nhọn là bắt buột, và dấu { phải nằm cùng dòng với post.

initialization được thực hiện trước khi vòng lặp bắt đầu. initialization phải là một câu lệnh đơn giản, có thể là câu lệnh khai báo biến ngắn gọn, toán tử tăng, giảm, câu lệnh gán, hay một lời gọi hàm.

condition là một biểu thức boolean, được tính toán mỗi khi vòng lặp bắt đầu, nếu trả về kết quả là true, các câu lệnh bên trong vòng lặp loop sẽ được thực thi, nếu trả về false thì vòng lặp sẽ két thúc.

post là câu lệnh thực thi sau cùng của mỗi vòng lặp.

Ba thành phần trên là tuỳ chọn và không bắt buộc, bạn có thể bỏ qua bất kỳ thành phần nào để tạo ra một dạng lặp mới:

  • Nếu bỏ qua initializationpost thì chúng ta sẽ có vòng lặp while:
// vòng lặp while
for condition {
    // ...
}
  • Nếu bỏ qua cả ba thành phần, ta sẽ có vòng lặp vô tận và có thể bị dừng lại bởi câu lệnh break hoặc return:
// vòng lặp vô tận
for {
    // ...
}

Một dạng khác của vòng lặp for là vòng lặp for range lặp qua một dãy các giá trị của các kiểu như string, slice, ... Để dễ hình dung, chúng ta sẽ đến với phiên bản 2 của echo (file echo2.go):

import(
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

Trong mỗi lần lặp, range tạo ra một cặp giá trị là index và giá trị của phần tử tại vị trí index của slice os.Args[1:]. Trong ví dụ trên chúng ta không cần dùng index, ta có thể gán giá trị index cho một biến như tmp_index và không dùng biến đó for tmp_index, arg := range os.Args[1:] { .... Nhưng Go không cho phép khai báo một biến local mà không dùng.

Để giải quyết vấn đề trên, Go cung cấp blank identifier (_, dấu gạch dưới). blank identifier được dùng bất cứ khi nào cú pháp yêu cầu khai báo một biến nhưng logic của chương trình không cần dùng đến. Giống như trên, cú pháp của for range yêu cầu 1 biến index và 1 biến value nhưng chúng ta chỉ dùng value và không cần index.

Trong phiên bản này, chúng ta khai báo hai biến ssep theo cách ngắn gọn và khởi tạo giá trị cho chúng. Có nhiều cách khai báo biến khác nhau:

s := ""
var s string
var s = ""
var s string = ""

Cách đầu tiên khá ngắn gọn, thường dùng để khai báo biến trong function, không dùng khai báo biến cấp độ package. Ví dụ: x, y := "a", 1.

Cách thứ hai khai báo kiểu biến và tự động khởi tạo gía trị zero của kiểu dữ liệu tương ứng.

Cách thứ ba ít khi được sử dụng, trừ khi khai báo nhiều biến cùng lúc. Ví dụ: var x, y = "a", 1

Cách thứ tư khai báo rõ ràng kiểu dữ liệu, dường như dư thừa khi mà nó giống với kiểu của giá trị khởi tạo nhưng cách này hữu ích trong một số trường hợp khi chúng không cùng kiểu.

Thông thường thì bạn chỉ dùng hai cách đầu tiên.

Với hai phiên bản trước của echo, mỗi lần lặp s nhận một giá trị hoan toàn mới. Câu lệnh += tạo ra một chuỗi hoàn toàn mới bằng cách nối chuỗi cũ với khoảng trắng và tham số tiếp theo rồi gán chuỗi mới ngược lại cho s. Giá trị cũ của s không còn được dùng nữa và sẽ bị dọn dẹp (garbage collection) trong quá trình đó. Nếu số lượng data lớn thì hiệu năng của chương trình sẽ giảm đáng kể. Giải pháp đơn giản và hiệu quả hơn là dùng hàm Join trong package strings (file echo3.go):

// Echo3 in tất cả các tham số command-line 
package main 

import(
    "fmt"
    "os"
    "strings"
)

func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
    // Ngoài ra bạn cũng có thể in thẳng ra slice với hàm `Println`: 
    // fmt.Println(os.Args[1:])
}

Bài tập dành cho bạn:

Exercise 1.1: Thay đổi chương trình echo để có thể in ra cả os.Args[0].

Exercise 1.2: Thay đổi chương trình echo để có thể in ra cả indexvalue của mỗi tham số. Mỗi cặp trên một dòng.

Reference: The Go Programming Language

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

nhaancs

11 bài viết.
3 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
7 2
Makefile thực hiện một số thao tác thường dùng trong Go Khi làm project Go mình thường tạo một file Makefile dạng này: Lưu ý nhớ thay thành tên m...
Huy Trần viết hơn 1 năm trước
7 2
White
39 15
Go là gì? Dùng nó cho việc gì? Chắc hẳn đến thời điểm hiện tại, không ai là chưa nghe đến Go (hay còn gọi là Golang), một ngôn ngữ lập trình được ...
Huy Trần viết hơn 2 năm trước
39 15
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
11 bài viết.
3 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!