Các hàm xử lý chuỗi nâng cao trong Bash (Phần 2)

Xin chào các bạn, chào mừng các bạn trở lại với series lập trình Bash của mình. Ở bài viết trước mình đã giới thiệu đến các bạn một số cách xử lý chuỗi nâng cao trong Bash. Để tiếp nối, trong bài viết này, mình sẽ tiếp tục nói về một số cách xử lý chuỗi rất hữu dụng và cũng khá đơn giản trong Bash.

Reverse a string case

Lưu ý: hàm dưới đây yêu cầu phiên bản bash từ 4. trở lên

reverse_case()
{
    # Usage: reverse_case "string"
    printf '%s\n' "${1~~}"
}

ví dụ:

$ reverse_case "hello"    # => HELLO

$ reverse_case "HeLlO"    # => hElLo

$ reverse_case "HELLO"    # => hello

Trim quotes from a string

trim_quotes()
{
     # Usage: trim_quotes "string"
     : "${1//\'}"
     printf '%s\n' "${_//\"}"
}

ví dụ:

var="'Hello', \"World\""
trim_quotes "$var"

Ta có thể thấy, hàm trên sử dụng phép replace trên chuỗi để thực hiện loại bỏ hết các ký tự ” \’ ” và ” \” ” cuối cùng ta có được kết quả là: Hello, World

Strip all instances of pattern from string

strip_all()
{
     # Usage: strip_all "string" "pattern"
     printf '%s\n' "${1//$2}"
}

với hàm trên, ta lại thấy được thủ thuật đơn giản replace trên chuỗi, chuỗi bị replace ở đây có thể là chuỗi, ký tự đặc biệt hoặc có thể là 1 tập hợp các ký tự.

ví dụ:

strip_all "The Quick Brown Fox" "[aeiou]"
# => Th Qck Brwn Fx

strip_all "The Quick Brown Fox" "[[:space:]]"
# => TheQuickBrownFox

strip_all "The Quick Brown Fox" "Quick "
# => The Brown Fox

Strip first occurrence of pattern from string

Ở trường hợp này, ta vẫn dùng thủ thuật replace string để thực hiện được muc đích của mình, nhưng thay vì dùng phép thay thế tất cả “\\” ta sẽ sử dụng phép thay thế “\” – thay thế chuỗi đầu tiên tìm thấy được.

strip()
{
      # Usage: strip "string" "pattern"
      printf '%s\n' "${1/$2}"
}

strip "The Quick Brown Fox" "[aeiou]"
# => Th Quick Brown Fox

strip "The Quick Brown Fox" "[[:space:]]"
# => TheQuick Brown Fox

Strip pattern from start of string

Để xóa 1 pattern ở đầu chuỗi nếu có, ta dùng thủ thuật cắt chuỗi “##” để cắt pattern khỏi chuỗi.

lstrip()
{
     # Usage: lstrip "string" "pattern"
     printf '%s\n' "${1##$2}"
}

lstrip "The Quick Brown Fox" "The "

với lệnh trên, ta sẽ được kết quả là: “Quick Brown Fox”, pattern “The ” đã bị cắt khỏi chuỗi.

Ngoài ra, ta còn có thể sử dụng pattern là một Regular Expression như sau:

lstrip “The Quick Brown Fox” “T*n”

ta sẽ được kết quả là: ” Fox”

 

Strip pattern from end of string

Ở trường hợp này, ta thực hiện giống như với trường hợp trước. Nhưng thay vì sử dụng phép cắt chuỗi “##”, cắt chuỗi dài nhất từ đầu chuỗi, ta thay thế với phép cắt chuỗi “%%”, cắt chuỗi dài nhất từ cuỗi chuỗi. Đương nhiên, có thể dụng pattern là môt chuổi Regular Expression.

rstrip()
{
      # Usage: rstrip "string" "pattern"
      printf '%s\n' "${1%%$2}"
}

rstrip "The Quick Brown Fox" " Fox"       #=>    "The Quick Brown"
rstrip "The Quick Brown Fox" "B*x"        #=>    "The Quick "

Percent-encode a string

urlencode()
{
     # Usage: urlencode "string"
     local LC_ALL=C
     for (( i = 0; i < ${#1}; i++ )); do
         : "${1:i:1}"
         case "$_" in
               [a-zA-Z0-9.~_-])
               printf '%s' "$_"
               ;;

         *)
               printf '%%%02X' "'$_"
               ;;
         esac
      done
      printf '\n'
}

Đầu tiên, ta hãy tìm hiểu 1 số khái niệm mới có trong hàm ở trên:

LC_ALL: là một biến môi trường có tác dụng ghi đè tất cả các thiết lập về địa điểm. Tất cả các sự khác biết về địa điểm (như ký tự phân cách của chữ số hàng nghìn thuộc hệ thập phân, bộ ký tự, thứ tự sắp xếp, ngày, tháng, năm, ngôn ngữ hoặc thông báo lỗi, ký tự tiền tệ ) đều có thể được cài đặt bằng cách sử dụng biến môi trường trên. Trong trường hợp sử dụng trong hàm ở trên, ta cài đặt LC_ALL=C để [a-z] trùng với 26 ký tự ASCII từ a đến z.

Tiếp theo, ta hãy cũng phân tích hàm ở trên:

Ta sẽ loop qua từng ký tự trong chuỗi input, để thực hiện điều này ta sử dụng vòng for chạy từ 0 đến độ dài của chuỗi input trừ đi 1, sau đó ta lấy ký tự tại vị trí thứ i ( : “${1:i:1}” ) và kiểm tra, nếu ký tự vị trí thứ i thuộc [a-zA-Z0-9.~_-] thì gán bằng string, nếu không phải thì chuyển sang dạng hex với 2 chữ số.

urlencode "https://github.com/dylanaraps/pure-bash-bible"
# =>    https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible

Decode a percent-encoded string

urldecode()
{
     # Usage: urldecode "string"
     : "${1//+/ }"
     printf '%b\n' "${_//%/\\x}"
}

Trong hàm Decode trên, ta dùng phép thay thế chuỗi để thay thế tất cả các ký tự “+” thành ký tự space. Sau đó ta thực hiện thay thế tất cả ký tự “%” thành ký tự “\x” bằng ${_//%/\\x}.

Sau đó ta dùng format %b để in ra chuỗi sau khi thay thế, ta sẽ được kết quả là chuỗi Decode

urldecode "https%3A%2F%2Fgithub.com%2Fdylanaraps%2Fpure-bash-bible"

ta sẽ được chuỗi sau khi thay thế là “https\x3A\x2F\x2Fgithub.com\x2Fdylanaraps\x2Fpure-bash-bible”

ta thực hiện in chuỗi trên với định dạng %b (in đối số tương ứng trong đó thực thi các ký tự đặc biệt có ký tự “\”, ví dụ ta thực hiện printf ‘%b\n’ “\x3A”  ta được kết quả là ký tự “:” Nếu để ý ta có thể thấy x3A là mã hóa số của ký tự unicode “:”)

=>     https://github.com/dylanaraps/pure-bash-bible

Check if string contains a sub-string

  • Sử dụng if để kiểm tra
if [[ $var == *sub_string* ]]; then
    printf '%s\n' "sub_string is in var."
fi

if [[ $var != *sub_string* ]]; then
    printf '%s\n' "sub_string is not in var."
fi

# cách kiểm tra này cũng hoạt động trên array
if [[ ${arr[*]} == *sub_string* ]]; then
    printf '%s\n' "sub_string is in array."
fi
  • Sử dụng case
case "$var" in
     *sub_string*)
          # Do stuff
          ;;

     *sub_string2*)
          # Do more stuff
          ;;

     *)
          # Else
          ;;
esac

Check if string starts with sub-string

if [[ $var == sub_string* ]]; then
    printf '%s\n' "var starts with sub_string."
fi

# Inverse (var does not start with sub_string).
if [[ $var != sub_string* ]]; then
    printf '%s\n' "var does not start with sub_string."
fi

Check if string ends with sub-string

if [[ $var == *sub_string ]]; then
    printf '%s\n' "var ends with sub_string."
fi

# Inverse (var does not end with sub_string).
if [[ $var != *sub_string ]]; then
    printf '%s\n' "var does not end with sub_string."
fi

Qua hai bài viết, chúng ta đã tìm hiểu được các cách xử lý chuỗi rất hữu dụng và rất thường được sử dụng trong việc lập trình mà không cần phải sử dụng các công cụ ngoài như sedawkperl. Bài viết tới, chúng ta sẽ bắt đầu với các cách xử lý nâng cao của một kiểu dữ liệu mới mà chúng ta thường xuyên sử dụng là mảng.

Các hàm xử lý chuỗi nâng cao trong Bash (Phần 1)

Các toán tử cơ bản, cấu trúc so sánh và mảng trong Bash

Các cấu trúc điều khiển, xử lý chuỗi đơn giản và Dictionaries trong Bash

Hướng dẫn cài đặt plugin Really Simple SSL để hiển thị khóa xanh với các website WordPress

Hướng dẫn cấu hình Outlook 2010

Hướng dẫn cài đặt nhanh NodeJS trên CentOS 7

Hướng dẫn cài đặt PHPMyAdmin trên DirectAdmin 1.59 trở lên

Hướng dẫn khôi phục mật khẩu quản trị (reset root password) trên máy chủ sử dụng hệ điều hành CentOS