Thứ Bảy, 7 tháng 9, 2019

Bắt đầu với Lập trình Shell - write script in linux


Bắt đầu với Lập trình Shell


Mặc dù chúng ta đã quá quen với shell script. Nhưng sau đây, tôi vẫn muốn giới thiệu về cách để viết 1 shell script cho những bạn đang cần nó.

1. Viết shell script như thế nào ?

Bước 1: dùng bất cứ chương trình gì có thể soạn thảo . Ví dụ: vi
  • Bạn nên dùng gedit để viết shell vì khi viết shell nó sẽ hiện màu sắc phân biệt giữa các kí tự và các từ khoá giúp bạn dễ kiểm soát lỗi. Bước 2: sau khi viết xong phải gán quyền thực thi cho script Ví dụ: $ chmod u+x tên script Bước 3: thực thi script Cú pháp:
$ bash tên_script
$ sh tên_script
$ ./ tên_script
Cấu trúc một chương trình shell script như sau:
#!/bin/bash <- shell mà script sẽ chạy
command … <- lệnh
command…
exit 0 <- thoát
Chú ý: lệnh exit 0 sẽ được mô tả kỹ trong phần Exit status

2. Biến trong shell

Trong linux shell thì có 2 kiểu biến:
  • Biến hệ thống (system variable): được tạo bởi Linux. Kiểu biến này thường được viết bằng ký tự in hoa.
  • Biến do người dùng định nghĩa. Định nghĩa biến: Cú pháp: tên biến=giá trịMột số quy định về biến trong shell: (1) Tên bắt đầu bằng ký tự hoặc dấu gạch chân (_). (2) Không được có khoảng trắng trước và sau dấu bằng khi gán giá trị cho biến (3) Biến có phân biệt chữ hoa chữ thường (4) Bạn có thể khai báo một biến có giá trị NULL như sau: var01= hoặc var01="" (5) Không dùng ?, * để đặt tên biến.

3. Sử dụng biến

Để truy xuất giá trị biến, dùng cú pháp sau: $tên_biến ví dụ:
n=10
echo $n
  1. Lệnh echo Dùng để hiển thị dòng văn bản, giá trị biến … Cú pháp: echo [options] [chuỗi, biến…] Các option:
-n: không in ký tự xuống dòng.
-e: cho phép hiểu những ký tự theo sau dấu \ trong chuỗi
\a: alert (tiếng chuông)
\b: backspace
\c: không xuống dòng
\n: xuống dòng
\r: về đầu dòng
\t: tab
\\: dấu \
ví dụ: echo –e "một hai ba \a\t\t bốn \n"

5. Tính toán trong Shell

Sử dụng expr Cú pháp: expr op1 phép toán op2 Ví dụ:
expr 1 \+ 3
expr 2 \– 1
expr 10 \/ 2
expr 20 \% 3
expr 10 \* 3
echo `expr 6 \+ 3`
z=`epxr $z \+ 3`
Sử dụng let Ví dụ:
let "z=$z+3"
let "z += 3"
let "z=$m*$n"
Sử dụng $((…)) ví dụ:
z=$((z+3))
z=$(($m*$n))
chú ý:
epxr 20 \% 3 : 20 mod 3
epxr 10 \* 3 : phép toán nhân , sự dụng \* chứ không phải * để phân biệt với ký tự thay thế.
Dòng cuối trong ví dụ trên được sử dụng rất nhiều trong shell, khi một lệnh được đặt giữa 2 dấu " (không phải dấu nháy đơn ‘ ’ ) thì shell sẽ thực thi lệnh đó. Ví dụ: a=epxr 10 * 3`` –> a sẽ có giá trị là 10 x 3 = 30 in kết quả ra màn hình: echo $a

6. Một vài thông tin về dấu ngoặc kép

Có 3 loại dấu sau: Code: ": Nháy kép bất cứ gì nằm trong dấy nháy kép được xem là những ký tự riêng biệt ‘: Nháy đơn những gì nằm trong dấu nháy đơn có ý nghĩa không đổi `: Nháy ngược thực thi lệnh Ví dụ:
echo "hôm nay là date" -> không in được hôm nay là thứ mấy
echo "hôm nay là `date`" -> sẽ in ra ngày tháng hôm nay vì date nằm trong dấu nháy ngược ` `

7. Trạng thái Exit

Mặc định trong Linux, khi một lệnh hoặc script thực thi , nó trả về 2 loạI giá trị để xác định xem lệnh hoặc script đó có thực thi thành công không. (1). Nếu giá trị trả về là 0 (zero) -> lệnh thực thi thành công (2). Nếu giá trị trả về khác 0 (nonzero) -> không thành công Giá trị đó gọI là Exit status Vậy làm thế nào để biết được giá trị trả về của một lệnh hay 1 script ? Rất đơn giản, chỉ cần sử dụng biến đặc biệt có sẵn của shell: $? Ví dụ: nếu bạn xoá 1 file không tồn tạI trên đĩa cứng
rm unknowfile
echo $? -> sẽ in ra màn hình một giá trị khác 0

8. Lệnh read – đọc giá trị nhập từ bàn phím , file …

Dùng để lấy dữ liệu nhập từ bàn phím và lưu vào biến Cú pháp: read var1 var2 var3 … varN read không có tham số  giá trị sẽ được chứa trong biến $REPLY ví dụ:
read
var="$REPLY"
Bình thường thì dấu \ cho phép xuống dòng để nhập tiếp dữ liệu trong read. Nếu read –rthì sẽ không có ý nghĩa đó. Ví dụ: read var # nhập vào: first line \ second lineecho "$var"  kết quả: first line second line Nhưng vớI tham số r thì sao ? read –r var # nhập vào: first line \ echo "$var"  kết quả: first line \ Lệnh read có thể dùng để đọc file. Nếu file chứa nhiều hơn 1 dòng thì chỉ có dòng thứ nhất được gán cho biến . Nếu read vớI nhiều hơn 1 biến (read var1 var2 …) thì read sẽ dựa vào biến $IFSđể gán dữ liệu cho các biến. Mặc định thì $IFS là khoảng trắng. Ví dụ: read var < data_file Nếu file có nhiều hơn 1 dòng read var1 var2 < data_file Khi đó, mỗI biến sẽ chứa 1 chuỗI được cách biệt bởI khoảng trắng ($IFS) chứ không phảI 1 dòng, biến cuốI cùng sẽ được chứa toàn bộ phần còn lạI của dòng. Vậy làm thế nào để đọc toàn bộ file ? Có thể giảI quyết bằng vòng lặp không ??
while read line
do
echo $line
done < data_file
Sử dụng $IFS (Internal File Separator ) để tách một dòng input của read, nếu bạn không muốn mặc định là khoảng trắng thì làm như thế nào ? Xem đoạn script sau:
echo "liet ke tat ca user "
OIFS=$IFS; IFS=: # backup lạI IFS và gán giá trị mới. Ví file /etc/passwd dùng: để tách biệt
các trường vớI nhau nên gán IFS là:
while read name passwd uid gid fullname ignore
do
echo "$name $fullname"
done < /etc/passwd # I/O redirection
IFS=$OIFS # trả lạI IFS ban đầu
Nếu đặt IFS ngay trong vòng lặp thì không cần backup IFS
while IFS=: read name passwd uid gid fullname ignore
do
echo "$name $fullname"
done < /etc/passwd
IFS vẫn không đổi

9. Tham số lệnh

Giả sử ta có script tên myself , để thực thi script này ta cần truyền vào 2 tham số như sau: $ myself one two Trong đó myself là tên script one: tham số thứ nhất truyền vào script two: tham số thứ hai Trong shell, bạn truy xuất đến những tham số như sau:myselflà $0 one là $1 two là $2 Và biến $# (có sẵn trong shell) sẽ cho giá trị 2 (có 2 tham số one và two). Bạn có thể lấy tất cả các tham số bằng cách sử dụng biến $@hoặc $*

10.Redirection

Hầu hết tất cả lệnh đều cho xuất kết quả ra màn hình hoặc lầy dữ liệu từ bàn phím nhưng vớI Linux bạn còn có thể xuất dữ liệu vào file và đọc dữ liệu từ file. Ví dụ: ls > filename # in kết quả lệnh ls vào file có tên filename. Có 3 ký hiệu redirection là >, >> và < (1). Ký hiệu > cú pháp: linux-command > filename Xuất output của lệnh ra file. Nếu file tồn tạI thì nó sẽ ghi đè còn nếu file chưa có thì tạo file mớI (2). Ký hiệu >>cú pháp: linux-command >> filename Xuất output của lệnh vào cuốI file nếu file đã tồn tạI, còn nếu file chưa tồn tạI thì tạo file mới. (3). Ký hiệu < cú pháp: linux-command < filename lấy dữ liệu cho linux-command từ filename thay vì từ bàn fím.

11. Pipe

Cú pháp: command 1 | command 2 Output của command 1 sẽ là dữ liệu vào của command 2 Ví dụ: $who | grep root

Kết

Trên đây là một số cú pháp cơ bản, mong có thể giúp được các bạn, không ít thì nhiều


Hướng dẫn viêt chương trình Shell Script Linux

1. Khái niệm cơ bản về Bash Script:
Trong những ngày đầu, máy tính chỉ hiểu được ngôn ngữ nhị phân, đó là điều khó khăn đối với người dùng và cả những nhà phát triển.
Theo nhu cầu đó,một chương trình đặc biệt ra đời, nó được gọi là Shell. Shell chấp nhận các câu lệnh tiếng Anh, theo một cú pháp cho trước và chuyển nó đển Kernel để xử lý tiếp, sau đó trả lại kết quả cho người dùng.
Shell là chương trình để tương tác giữa người dùng và máy tính. Thông dịch các lệnh của người dùng nhập vào hoặc từ các file.
Shell không phải là một phần của hạt nhân hệ thống, nhưng nó dùng hạn nhân để thực thi các chương trình, tạo ra các tập tin…
Các loại Shell Script:
Một số Shell có sẵn trên Linux:
  • BASH ( Bourne-Again SHell ) phát triển bởi Brian Fox và Chet Ramey. Đây là Shell thông dụng nhất trên Linux.
  • CSH (C SHell) phát triển bởi Bill Joy tại University of California (dành cho BSD). Sử dụng cấu trúc lệnh giống C, rất thân thiện cho các lập trình viên C trên linux.
  • KSH (Korn SHell) phát triển bởi David Korn tại AT & T Bell Labs.
  • TCSH bạn có thể gọ lệnh #man tcsh để xem thông tin.
Để xem hệ thống của bạn hỗ trợ những loại shell nào, có thể dùng lệnh #cat /etc/shells. Mỗi loại shell có cú pháp, cách dùng khác nhau, cung cấp cho bạn các chức năng khác nhau.
Trong MS-DOS, Shell là được cọi là COMMAND.COM, cũng được sử dụng cho mục đích tương tự, nhưng không mạnh mẽ bằng Shell Linux.
Tất cả các loại Shell phía trên đều đọc các lệnh người dùng nhập và đưa đến Linux OS để nói với OS rằng người dùng đang muốn gì. Các dòng lệnh được gọi là command line.
Để xem system đang chạy shell gì, bạn có thể dùng lệnh #echo $SHELL
Hello World script:
Đến đây, để khỏi nhàm chán, chúng ta cùng làm 1 bài đơn giản nhất, nhập môn của bất kỳ ngôn ngử nào. Hello World. Tạo 1 file tên là hello_world.sh, nội dung như sau:
#!/bin/bash
# declare STRING variable
STRING=”Hello World”
#print variable on a screen
echo $STRING
Dòng đầu tiên chúng ta luôn đặt #!/bin/bash, đây là cú pháp bắt buộc. Sau # được hiểu là comment, chú thích của các đoạn mã.
  • STRING=”Hello World” // Khai báo biến STRING.
  • echo $STRING // Hiển thị biến STRING ra màn hình.
Lưu file lại, tiến hành chmod cho file:
#chmod 755 hello_world.sh
Chạy file:
sh hello_world.sh
Kết quả sẽ nhận về là : Hello World
2. Một số ví dụ đơn giản sẽ giúp các bạn làm quen dần với Bash Shell Script, viết được một số ứng dụng cơ bản cho mình.
Backup thư mục đơn giản:
#!/bin/bash
tar -czf www.tar.gz /home/www
Đoạn script trên giúp ta nén lại thư mục /home/www thành file www.tar.gz, như thế ta đã có một bản backup của thư mục này.
Biến: Variables
Cùng xem qua lại ví dụ Hello World đã làm ở trên để xem kỹ hơn về biến, khai báo biến:
#!/bin/bash
STRING=”Hello World”
echo $STRING
Vậy hãy cũng làm lại script Backup phía trên, trong đó có khai báo biến.
#!/bin/bash
OF=www_$(date +%Y%m%d).tar.gz
tar -czf $OF /home/www
Biến toàn cục và cục bộ: Global vs. Local variables
Làm ví dụ sau:
#!/bin/bash
#Define bash global variable
#This variable is global and can be used anywhere in this bash script
VAR=”global variable”
function bash {
#Define bash local variable
#This variable is local to bash function only
local VAR=”local variable”
echo $VAR
}
echo $VAR
bash
# Note the bash global variable did not change
# “local” is bash reserved word
echo $VAR
Kết quả sẽ tra về:
global variable
local variable
global variable
Đối số cho Bash Script: Passing arguments to the bash script
#!/bin/bash
# use predefined variables to access passed arguments
#echo arguments to the shell
echo $1 $2 $3 ‘ -> echo $1 $2 $3′
# We can also store arguments from bash <a title=”See the tag: command (5 posts)” class=”autobesttag” rel=”nofollow” href=”http://gocit.vn/command/”>command</a> line in special array
args=(“$@”)
#echo arguments to the shell
echo ${args[0] } ${args[1] } ${args[2] } ‘ -> args=(“$@”); echo ${args[0] } ${args[1] } ${args[2]
}’
#use $@ to print out all arguments at once
echo $@ ‘ -> echo $@’
# use $# variable to print out
# number of arguments passed to the bash script
echo Number of arguments passed: $# ‘ -> echo Number of arguments passed: $#’
Kết quả:
Bash Shell Script -> echo $1 $2 $3
Bash Shell Script -> args=(“$@”); echo ${args[0] } ${args[1] } ${args[2]
}
Bash Shell Script -> echo $@
Number of arguments passed: 3 -> echo Number of arguments passed: $#
Thực thi lệnh với Bash: Executing shell commands with bash
#!/bin/bash
# use backticks ” ` ` ” to execute shell command
echo `uname -o`
# executing bash command without backticks
echo uname -o
Kết quả:
GNU/Linux
uname -o
Đọc dữ liệu đầu vào: Reading User Input
#!/bin/bash
echo -e “Hi, please type the word: \c ”
read word
echo “The word you entered is: $word”
echo -e “Can you please enter two words? ”
read word1 word2
echo “Here is your input: \”$word1\” \”$word2\”"
echo -e “How do you feel about bash scripting? ”
# read command now stores a reply into the default build-in variable $REPLY
read
echo “You said $REPLY, I’m glad to hear that! ”
echo -e “What are your favorite colours ? ”
# -a makes read command to read into an array
read -a colours
echo “My favorite colours are also ${colours[0] }, ${colours[1] } and ${colours[2] }:-)”
Bash Trap Command
Phần này sẽ là một ví dụ thực thi lệnh khi dữ liệu đầu vào là tổ hợp phím CTRL+C.
#!/bin/bash
# bash trap command
trap bashtrap INT
# bash clear screen command
clear;
# bash trap function is executed when CTRL-C is pressed:
# bash prints message =&gt; Executing bash trap subrutine !
bashtrap()
{
echo “CTRL+C Detected !…executing bash trap !”
}
# for loop from 1/10 to 10/10
for a in `seq 1 10`; do
echo “$a/10 to Exit.”
sleep 1;
done
echo “Exit Bash Trap Example!!!”
Kết quả khi chạy Bash file:
Debian Linux
Redhat Linux
Ubuntu
Linux
Như vậy nếu không đặt phần tử mảng trong “ thì mặc định các phần tử được cắt ra bởi khoảng trắng (space)
Mảng: Array
#!/bin/bash
#Declare array with 4 elements
ARRAY=( ‘Debian Linux’ ‘<a title=”See the tag: redhat (2 posts)” class=”autobesttag” rel=”nofollow” href=”http://gocit.vn/centos/”>Centos</a> Linux’ <a title=”See the tag: ubuntu (4 posts)” class=”autobesttag” rel=”nofollow” href=”http://gocit.vn/ubuntu/”>Ubuntu</a> Linux )
# get number of elements in the array
ELEMENTS=${#ARRAY[@]
}
# echo each element in array
# for loop
for (( i=0;i&lt;$ELEMENTS;i++)); do
echo ${ARRAY[${i}]
}
done
Kết quả trả về :
Debian Linux
Redhat Linux
Ubuntu
Linux
Như vậy, nếu không khai báo phần tử mảng trong “ thì mặc định các phần tử được xác định bởi khoảng trắng (space).
Điều kiện If / Else: Bash if / else / fi statements
#!/bin/bash
directory=”./BashScripting”
# bash check if directory exists
if [ -d $directory ]
; then
echo “Directory exists”
else
echo “Directory does not exists”
fi
Lưu ý có khoảng trắng giữa [ và ] , nếu không thì bash sẽ không chạy được do sai cú pháp. Mở đầu bằngif và kết thúc bằng fi.
Các điều kiện If / else có thể lồng vào nhau: Nested if/else
#!/bin/bash
# Declare variable choice and assign value 4
choice=4
# Print to stdout
echo “1. Bash”
echo “2. Scripting”
echo “3. Tutorial”
echo -n “Please choose a word [1,2 or 3]
? ”
# Loop while the variable choice is equal 4
# bash while loop
while [ $choice -eq 4 ]
; do
# read user input
read choice
# bash nested if/else
if [ $choice -eq 1 ]
; then
echo “You have chosen word: Bash”
else
if [ $choice -eq 2 ]
; then
echo “You have chosen word: Scripting”
else
if [ $choice -eq 3 ]
; then
echo “You have chosen word: Tutorial”
else
echo “Please make a choice between 1-3 !”
echo “1. Bash”
echo “2. Scripting”
echo “3. Tutorial”
echo -n “Please choose a word [1,2 or 3]
? ”
choice=4
fi
fi
fi
done
Cũng như các ngôn ngữ lập trình, trong Bash Shell Script cũng có các phép toán so sánh. Nó có khả năng so sánh, đối chiếu toán tử và các chuỗi. Hãy cùng xem qua các ví dụ chi tiết của từng phần sau.
So sánh toán tử: Arithmetic Comparisons
-lt<
-gt>
-le<=
-ge>=
-eq==
-ne!=
Ví dụ cho phép so sánh toán tử:
#!/bin/bash
# declare integers
NUM1=2
NUM2=2
if [ $NUM1 -eq $NUM2 ]
; then
echo “Both Values are equal”
else
echo “Values are NOT equal”
fi
Kết quả khi chạy bash sẽ được: Both Values are equal vì cả hai biến NUM1 và NUM2 đều có giá trị như nhau, đều = 2.
So sánh chuỗi: String Comparisons
=equal
!=not equal
<less then
>greater then
-n s1string s1 is not empty
-z s1string s1 is empty
Ví dụ minh họa:
>#!/bin/bash
#Declare string S1
S1=”Bash”
#Declare string S2
S2=”Scripting”
if [ $S1 = $S2 ]
; then
echo “Both Strings are equal”
else
echo “Strings are NOT equal”
fi
kết quả nhận được sẽ là: Strings are NOT equal vì hai chuỗi ký tự hoàn toàn khác nhau.
Kiểm tra file trong Bash: Bash File Testing
-b filenameBlock special file
-c filenameSpecial character file
-d directorynameCheck for directory existence
-e filenameCheck for file existence
-f filenameCheck for regular file existence not a directory
-G filenameCheck if file exists and is owned by effective group ID.
-g filenametrue if file exists and is set-group-id.
-k filenameSticky bit
-L filenameSymbolic link
-O filenameTrue if file exists and is owned by the effective user id.
-r filenameCheck if file is a readable
-S filenameCheck if file is socket
-s filenameCheck if file is nonzero size
-u filenameCheck if file set-ser-id bit is set
-w filenameCheck if file is writable
-x filenameCheck if file is executable
Ví dụ 1: Kiểm tra và thông báo file có tồn tại hay không.
#!/bin/bash
file=”./file”
if [ -e $file ]
; then
echo “File exists”
else
echo “File does not exists”
fi
Ví dụ 2: Chờ đến khi file được khởi tạo hoặc tồn tại thì thoát script.
#!/bin/bash
while [ ! -e myfile ]
; do
# Sleep until file does exists/is created
sleep 1
done
Vòng lặp FOR
Thể hiện danh sách các file trong folder định sẵn:
# !/bin/bash
# bash for loop
for f in $( ls /var/ ); do
echo $f
done
Hoặc bạn cũng có thể chạy dưới dạng command line:
# for f in $( ls /var/ ); do echo $f; done
Vòng lặp WHILE
Đoạn code mẫu ví dụ trong vòng lặp while:
#!/bin/bash
COUNT=6
# bash while loop
while [ $COUNT -gt 0 ]
; do
echo Value of count is: $COUNT
let COUNT=COUNT-1
done
Mã trên sẽ thể hiện ra màn hình từ 6 giảm dần về đến 1.
Vòng lặp UNTIL
Ví dụ mẫu về vòng lặp until:
#!/bin/bash
COUNT=0
# bash until loop
until [ $COUNT -gt 5 ]
; do
echo Value of count is: $COUNT
let COUNT=COUNT+1
done
Ngược lại với ví dụ về while. Vòng lặp until phía trên sẽ thể hiện ra màn hình tăng dần từ 1 đến 5.
Viết Function trong bash shell script
Cũng như các ngôn ngữ khác, bash shell cũng hỗ trợ function, chúng ta cùng xem qua cách viết và sử dụng trong ví dụ bên dưới:
!/bin/bash
# BASH FUNCTIONS CAN BE DECLARED IN ANY ORDER
function function_B {
echo Function B.
}
function function_A {
echo $1
}
function function_D {
echo Function D.
}
function function_C {
echo $1
}
# FUNCTION CALLS
# Pass parameter to function A
function_A “Function A.”
function_B
# Pass parameter to function C
function_C “Function C.”
function_D
Bash Select
#!/bin/bash
PS3=’Choose one word: ‘
# bash select
select word in “linux” “bash” “scripting” “tutorial”
do
echo “The word you have selected is: $word”
# Break, otherwise endless loop
break
done
exit 0
Bash select cho phép người dùng lựa chọn các option để đưa vào chương trình. Từ đó server cũng có thể lấy được giá trị người dùng vừa nhập để có thể chọn thuật toán xử lý chính xác.
Bash shell script backup MySQL database trên server Linux
Nếu ai đang sử dụng các dịch vụ VPS, Dedicated Server dùng cho nhu cầu Webserver hoặc các ứng dụng đang chạy trên nền tảng Linux và hệ cơ sở dữ liệu MySQL thì đây có thể là 1 đọan shell hữu dụng.
Nó phục vụ nhu cầu thực tế là backup và lưu trữ các file backup của MySQL dưới dạng tenfile_ngay_thang_nam.sql (Tên file, ngày, tháng, năm đã backup cơ sở dữ liệu này).
Tất nhiên để có thể hoàn chỉnh một công cụ backup, bạn cần phải thêm cho nó một số tính năng nữa như là upload file backup nào sang lưu trữ chỗ khác, thông qua giao thức FTP chẳng hạn, cấu hình hẹn giờ để backup…Nhưng có lẽ việc khởi đầu không thông qua bài “hello world” đã là khó khăn rồi.Chúng tôi chỉ dừng lại ở chức năng backup database MySQL và đưa file backup đó ra thư mục cấu hình sẵn mà thôi.
Tạo thư mục scripts, sau đó tạo file backup-database với nội dung như sau:
#!/bin/bash
echo Starting Backup
mysqldump web_data > /backup/database/web_data_`date +%e-%m-%y`.sql
echo Backup Finished
Quá đơn giản phải không?
  • Trong đó lệnh echo để thể hiện Text, đây cũng có thể gọi là “hello world” vậy.
  • Dòng mysqldump là câu lệnh backup database web_data ghi ra file web_data_ngay_thang_nam.sql trong thư mục /backup/database.
  • Dòng thứ 3 báo hiệu đã backup xong.
Để chạy file backup ta có thể dùng sh,bash trong của sổ SSH.Ví dụ:
sh /scripts/backup-database
Nếu bạn thấy thông báo Backup Finished có nghĩa là đã thành công, có thể vào thư mục /backup/database để kiểm tra thành quả.Ngoài ra, bạn cũng có thể kết hợp với crontab để cho backup được chạy tự động.

****** Nguồn bài viết http://www.gocit.vn/bai-viet/bash-shell-script/ *****

Không có nhận xét nào:

Đăng nhận xét