Perl là một ngôn ngữ phi định dạng kiểu như C - khoảng trắng giữa các phần tử của chương trình là tuỳ chọn, trừ phi hai phần tử của chương trình dính liền với nhau có thể bị lầm lẫn thàn
Trang 1về hệ thống báo lỗi, và lệnh awk và đưa ra bản đầu tiên của Perl Sau đó Larry đưa nó cho các độc giả Usenet, thường vẫn được gọi là “the Net” Kết quả là Perl phát triển dần
và cũng cùng tỉ lệ như kernel của UNIX Nó đã phát triển các tính năng và tính khả chuyển
Larry sẽ đưa ra bản Perl mới nhất, bản 5.0, chắc chắn sẽ có một số tính năng thường hay được yêu cầu, và được thiết kế lại từ bên trong trở ra Tuy nhiên, cuốn sách này đã được thử với Perl bản 4.0 Mọi thứ ở đây đều sẽ làm việc với bản 5.0 và các bản sau của Perl Trong thực tế, chương trình Perl 1.0 vẫn làm việc tốt với những bản gần đây, ngoại trừ một vài thay đổi khác cần cho sự nâng cấp
1.2 Mục đích của Perl
Perl được thiết kế để trợ giúp cho người dùng UNIX với những nhiệm vụ thông dụng mà
có thể rất nhậy cảm với tính khả chuyển đối với trình shell, vì nó ngắn hơn và không phức tạp như các ngôn ngữ lập trinh C hay một ngôn ngữ công cụ UNIX nào khác.Khi đã quen thuộc với Perl, bạn sẽ mất ít thời gian để lấy được các trích dẫn dòng lệnh shell (hay khai báo C), và mất ít thời gian để lập trình vì Perl là một công cụ trợ giúp tuyệt vời Các cấu trúc chặt chẽ của Perl cho phép tạo ra một số giải pháp với những công cụ mang tính tổng quát Cũng vậy, bạn có thể lấy những công cụ này sang công việc tiếp, vì Perl có tính khả chuyển cao và lại có sẵn trên hầu hết các hệ thống
Giống như mọi ngôn ngữ khác, Perl có thể “chỉ viết” - tức là có thể viết ra chương trình cho máy hiểu mà con người không thể "đọc hiểu" được Nhưng nếu chú ý cẩn thận, bạn có thể tránh được điều này Thật vậy, đôi khi Perl trông như khó với những ai
không quen, nhưng với người lập trình đã thạo Perl, nó cũng khá dễ (???) Nếu bạn làm theo những hướng dẫn trong cuốn sách này thì chương trình của bạn sẽ dễ đọc, dễ bảo trì và nâng cấp
1.3 Tính sẵn có
Nếu bạn nhận được một lỗi nhỏ: Perl: not found khi bạn thử gọi Perl từ dòng lệnh shell thì điều đó có nghĩa là Perl chưa được cài đặt trên hệ thống của bạn Nhưng bạn hoàn toàn có thể lấy được Perl miễn phí vài cài vào hệ thốgn của mình Perl được phân phối theo Giấy phép Công cộng GNU (GNU Public License), nghĩa là bạn có thể phân phát bản binary của Perl với điều kiện là phải kèm theo mã ngồn miễn phí; và nếu bạn sửa đổi Perl thep ý bạn bạn cũng phải phân phối mã nguồn của phần mà bạn sửa đổi Bạn
có thể lấy mã nguồn của Perl hoàn toàn miễn phí qua đường download mà chỉ tốn vài
MB để lưu trữ và một khoảng thời gian để tải xuống
Trong thực tế, nó không chỉ là miễn phí mà nó chạy còn gọn hơn trên gần như mọi thứ
mà có thể gọi là UNIX hay tựa UNIX và có trình biên dịch C Đó là vì là Perl có 1 phần được gọi là "Cấu hình" sẽ có nhiệm vụ gọi vào các danh mục hệ thống để tìm những
Trang 2thứ nó cần, và điều chỉnh việc đưa vào các tệp và các kí hiệu được xác định tương ứng,
và cuối cùng nó sẽ chuyển cho bạn việc kiểm chứng phát hiện của nó
Bên cạnh các hệ thống UNIX hay tựa UNIX, các "tín đồ" của Perl đã đem nó sang
Amiga, Atari ST, họ Macintosh, VMS, OS/2, thậm chí MS/DOS, Windows - và có lẽ còn nhiều hơn nữa khi bạn đang đọc những dòng chữ này Vị trí chính xác và sự có sẵn của những bản Perl này thì biến động, cho nên phải hỏi trên nhóm tin Usenet chẳng hạn để
có được thông tin mới nhất Nếu bạn là một newbie, thì một bản cũ của Perl đã có trên đĩa phần mềm CD-ROM UNIX Power Tools, của Jerry Peek, Tim O’Reilly và Mike
Loukides (O’Reilly & Associates/ Random House Co., 1993), hoặc bạn có thể tìm và download ở http://www.perl.org/
1.4 Hỗ trợ (nếu như có khúc mắc)
Bạn có thể liên hệ trực tiếp với Larry hoặc nhóm hỗ trợ Perl trực tuyến toàn thế giới, liên lạc thông qua nhóm tin Usenet comp.lang.perl hoặc gửi yêu cầu tới perl-users-request@virgina.edu
Bên cạnh nhóm tin, bạn cũng nên đọc tạp chí Perl, đi cùng việc phân phối Perl Một nguồn có thẩm quyền khác là cuốn sách Programming Perl của Larry Wall và Randal L Schwatrz (O’Reilly & Associaté, 1990)
1.5 Các khái niệm cơ bản về Perl
Một kịch bản shell là một dãy các lệnh shell đưa vào trong một tệp văn bản Tệp này chạy bằng cách bật một bit thực hiện (qua chmod +x filename) rồi gõ tên của tệp đó tại dấu nhắc của shell Chẳng hạn, một kịch bản shell để chạy lệnh date và sau đó là lệnh who sẽ được viết như sau:
$ echo date > somecript
$ echo who >> somecript
chương trình shell, nên chúng ta cần một bước phụ: đặt #!/usr/bin/perl làm dòng đầu tiên của tệp này Nhưng nếu Perl của bạn được cài vào nơi không chuẩn (không phải
là /usr/bin), hay hệ thống tựa UNIX của bạn không hiểu dòng #!, thì bạn hỏi người quản trị hệ thống của bạn để được giúp đỡ Các thí dụ trong sách này giả sử rằng hệ thống của bạn đã được cài đặt Perl vào /usr/bin
Perl là một ngôn ngữ phi định dạng kiểu như C - khoảng trắng giữa các phần tử của chương trình là tuỳ chọn, trừ phi hai phần tử của chương trình dính liền với nhau có thể
bị lầm lẫn thành một cái khác, trong trường hợp đó thì khoảng trắng thuộc loại nào đó
là bắt buộc (Khoảng trắng bao gồm dấu cách, dấu tab, xuống dòng, về đầu dòng hay
Trang 3sang trang mới) Có một vài cấu trúc đòi hỏi một loại khoảng trắng nào đó ở chỗ nào
đó, nhưng đừng lo, tài liệu sẽ chỉ rõ cho bạn biết là các khoảng trắng phải được đặt như thế nào với số lượng bao nhiêu Có thể giả thiết rằng loại và số lượng khoảng trắng giữa các phần tử trong chương trình là tuỳ ý trong các trường hợp khác
Mặc dù gần như tất cả các chương trình Perl đều có thể được viết tất cả trên một dòng, nhưng một chương trình Perl điển hình được viết xuống dòng và canh lề như chương trình C, với những phần câu lệnh lồng nhau được viết vào sâu hơn một chút chẳng hạn,
sẽ làm cho chương trình của bạn dễ nhìn và dễ hiểu hơn
Cũng giống như một kịch bản shell, chương trình Perl bao gồm tất cả các câu lệnh perl
về tệp được lấy tổ hợp chung như một trình lớn cần thực hiện Không có khái niệm về hàm chính main như trong C
Chú thích của Perl giống như chú thích của kịch bản shell: bất kì cái gì nằm giữa một dấu # tới cuối dòng đều là một chú thích Perl không có chú thích trên nhiều dòng như C
Không giống hầu hết các shell (nhưng giống như awk và sed), bộ thông dịch Perl phân tích và biên dịch hoàn toàn chương trình trước khi thực hiện nó Điều này có nghĩa là bạn không bao giờ nhận được lỗi cú pháp từ chương trình một khi chương trình đã bắt đầu chạy, và cũng có nghĩa là khoảng trắng và chú thích sẽ được lược bỏ mất và sẽ không làm chậm chương trình Trong thực tế, giai đoạn biên dịch này bảo đảm việc thực hiện nhanh chóng của các thao tác Perl một khi nó được bắt đầu, và nó cung cấp động cơ phụ để loại bỏ C như một ngôn ngữ tiện ích hệ thống nhất đơn thuần dựa trên nền tảng là C được coi là trình biên dịch
Việc biên dịch này khá tiêu tốn thời gian Và sẽ là phi hiệu quả nếu một chương trình Perl lớn lại chỉ thực hiện một nhiệm vụ nhỏ bé (trong số nhiều nhiệm vụ tiềm năng) và rồi thoát, vì thời gian chạy cho chương trình sẽ "nhỏ xíu" nếu so với thời gian biên dịch.Cho nên Perl giống như một bộ biên dịch và thông dịch Nó là biên dịch vì chương trình được đọc và phân tích hoàn toàn trước khi câu lệnh đầu tiên được thực hiện Nó là bộ thông dịch vì không có mã đích chiếm không gian đĩa Hiểu theo một cách nào đó, nó là tốt nhất cho cả hai loại này
1.6 Sơ lược về Perl
Sau đây sẽ giới thiệu một số các tính năng khác nhau bằng cách bổ sung vào một ứng dụng nhỏ Giải thích ở đây ngắn gọn - mỗi vùng chủ đề đều được thảo luận chi tiết hơn nhiều về sau trong cuốn sách này Nhưng việc "xem qua" này sẽ cho bạn kinh nghiệm nhanh chóng về ngôn ngữ
Trang 4này, tổ hợp kí tự \n biểu thị cho kí tự dòng mới Giống như trong C, tất cả các câu lệnh đơn giản đều kết thúc bằng dấu chấm phảy (;).
Khi bạn gọi chương trình này, phần kernel sẽ gọi bộ thông dịch Perl, phân tích toàn bộ chương trình (hai dòng, kể cả dòng chú thích đầu tiên) và thực hiện dạng đã dịch Thao tác đầu tiên và duy nhất là thực hiện hàm print Sau khi chương trình đã hoàn tất, thì tiến trình Perl sẽ trả về một mã cho shell để báo rằng chương trình đã kết thúc
1.6.2 Hỏi câu hỏi và nhớ kết quả
Ta hãy viết lại chương trình ở trên một chút, thay vì Hello trống không, chương trình sẽ chào tên người nhập vào từ bàn phím Để làm việc này, ta cần một chỗ lưu giữ tên, một cách hỏi tên, và một cách nhận câu trả lời
Để đặt chỗ giữ giá trị (VD: tên) ta dùng biến vô hướng Với chương trình này, ta sẽ dùng biến vô hướng $name để giữ tên (xem chi tiết trong chương sau: Dữ liệu vô
hướng, về những gì mà biến này có thể giữ, và những gì có thể làm với chúng) Bây giờ, giả sử rằng bạn có thể giữ một số hay một xâu (dãy các kí tự) trong biến vô
hướng
Chương trình này cần hỏi về tên Để làm điều đó, ta cần một cách nhắc và một cách nhận vào Chương trình trước đã chỉ ra cho ta cách nhắc - dùng hàm print Và để nhận một dòng từ thiết bị cuối ta dùng toán tử <STDIN> Ta dữ liệu nhập vào cho biến
$name:
print "Ten ban la gi? ";
$name = <STDIN>;
Giá trị của $name tại điểm này có một ký tự xuống dòng ở cuối (vì bạn phải nhấn Enter
để kết thúc việc nhập tên) Để bỏ qua kí tự đó, chúng ta dùng hàm chop() Hàm này lấy một biến vô hướng làm đối số duy nhất và bỏ đi ký tự cuối từ giá trị xâu của biến:chop($name);
Sau đó in ra câu chào:
print "Xin chao ban $name";
Bây giờ ta muốn có một lời chào đặc biệt cho Jenny, nhưng muốn lời chào thông
thường cho mọi người khác Để làm điều này, ta cần so sánh tên đã được đưa vào với xâu Jenny, và nếu hai xâu trùng khớp, thì làm điều gì đó đặc biệt Ta hãy bổ sung thêm lệnh if-then-else và phép so sánh vào chương trình:
#!/usr/bin/perl
print "Ten ban la gi? ";
$name = <STDIN>;
chop($name);
Trang 5if ($name eq "Jenny") {
#chao Jenny
print "Chao Jenny! Lam bai tap di chu!\n";
} else {
#chao binh thuong
print "Xin chao ban $name!\n";
}
Toán tử eq so sánh hai xâu Nếu bằng nhau (từng kí tự một, và có cùng chiều dài) kết quả sẽ là True, ngược lại sẽ là False Câu lệnh if chọn xem "khối" câu lệnh nào so sánh đúng được thực hiện - nếu biểu thức ($name eq "Jenny") là True, khối chưa câu lệnh print "Chao Jenny! Lam bai tap di chu!\n"; sẽ được thực hiện, còn trong trường hợp ngược lại sẽ là khối chưa câu lệnh print "Xin chao ban $name!\n";
1.6.4 Đoán từ bí mật
Để một người chạy chương trình đoán một từ bí mật Với mọi người trừ Jenny, ta hãy
để cho cho chương trình cứ hỏi lặp lại để đoán đến khi nào người này đoán được đúng Hãy xem chương trình sau:
#!/usr/bin/perl
$secretword = "Jenny"; #tu bi mat
print "Ten ban la gi? ";
print "Xin chao ban $name!\n";
print "Ban thu doan xem toi ten la gi? ";
$guess = <STDIN>;
chop($guess);
while ($guess ne $secretword) {
print "Sai roi, doan lai di";
$guess = <STDIN>;
chop($guess);
}
}
Trước hết, ta định nghĩa tên cần đoán bằng việc đặt nó vào trong biến vô hướng khác,
$secretword Sau khi đón chào, một người (không phải Jenny) sẽ được yêu cầu (với một câu lệnh print khác) đoán chữ Lời đoán được đem ra so sánh với từ cần đoán bằng việc dùng toán tử ne, mà sẽ cho True nếu các xâu này không bằng nhau (ne là toán tử ngược với toán tử eq) Kết quả của việc so sánh sẽ kiểm soát while, chu trình này thực hiện khi việc so sánh vẫn còn True
1.6.5 Nhiều từ bí mật
Trang 6Ta hãy xem cách thức mình có thể sửa đổi đoạn chương trình này để cho phép có nhiều
từ bí mật Bằng việc dùng điều ta đã thấy, chúng ta có thể so sánh lời đoán lặp đi lặp lại theo một chuỗi câu trả lời rõ được cất giữ trong các biến vô hướng tách biệt Tuy nhiên, một danh sách như vậy sẽ khó mà thay đổi hay đọc vào từ một tệp hay thiết bị khác
Một giải pháp tốt hơn là cất giữ tất cả các câu trả lời có thể vào trong một cấu trúc dữ liệu gọi là danh sách hay mảng Mỗi phần tử của mảng đều là một biến vô hướng tách biệt mà có thể được đặt giá trị hay truy cập độc lập Toàn bộ mảng cũng có thể được trao cho một giá trị để ta có thể "truy nhập" vào Ta có thể gán một giá trị cho toàn bộ mảng có tên @words sao cho nó chứa ba mật hiệu:
@words =("littlecat", "smalldog", "bigmouse");
Tên biến mảng bắt đầu với @, cho nên chúng là khác biệt với các tên biến vô hướng Một khi mảng đã được gán thì ta có thể truy cập đến từng phần tử bằng cách dùng một tham khảo chỉ số Cho nên $words[0] là littlecat, $words[1] là smalldog, $words[2] là bigmouse Chỉ số cũng có thể là một biểu thức, cho nên nếu ta đặt $i là 2 thì
$words[$i] là bigmouse (Tham khảo chỉ số bắt đầu với $ thay vì @ là do chúng tham khảo tới một phần tử riêng của mảng thay vì toàn bộ mảng) Quay trở lại với thí dụ trước đây của ta:
#!/usr/bin/perl
#cac tu bi mat
@words = ("littlecat", "smalldog", "bigmouse");
print "Ten ban la gi? ";
print "Xin chao ban $name!\n";
print "Tu bi mat la gi";
$guess = <STDIN>;
chop($guess);
$i = 0; #thu truoc 1 tu
$correct = "co the"; #tu nay co doan dung hay khong?
while ($correct eq "co the") { #cu kiem tra den khi het
Trang 7$i = 0; #kiem tra lai tu dau 1 lan nua
}
} #key thuc cua while ($correct eq "co the")
} #Ket thuc cua "not Jenny"
Chú ý rằng chúng ta đang dùng biến vô hướng $correct để chỉ ra rằng chúng ta vẫn đang tìm ra mật hiệu đúng, hoặc chúng ta không tìm thấy Chương trình này cũng giới thiệu khối elsif của câu lệnh if-then-else Không có lệnh nào tương đương như thế trong
C hay awk - đó là việc viết tắt của else và theo sau là một if mới nhưng không lồng trong cặp dấu () khác Đây chính là cái rất giống Perl để so sánh một tập các điều kiện trong việc phân tầng if-elsif-elsif-elsif-else
1.6.6 Cho mỗi người một từ bí mật khác nhau
Trong chương trình trước, bất kì người nào tới cũng đều có thể đoán bất kì từ nào trong
ba từ này và có thể thành công Nếu ta muốn từ bí mật là khác nhau cho mỗi người, thì
Trang 8print "Xin chao ban $name!\n"; #chao thuong thuong
$secretword = $words{$name}; #lay tu bi mat
print "Tu bi mat la gi? ";
$guess = <STDIN>;
chop($guess);
while ($correct ne $secretword) {
print "Sai roi thu lai di Tu bi mat la gi? ";
$guess = <STDIN>;
chop($guess);
} #ket thuc cua while
}
Bạn hãy chú ý nhìn vào từ bí mật Nếu không tìm thấy từ này thì giá trị của
$secretword sẽ là một xâu rỗng, mà ta có thể kiểm tra liệu ta có muốn xác định một từ
bí mật mặc định cho ai đó khác không Đây là cách xem nó:
#
$secretword = $words{$name}; #lay tu bi mat
if ($secretword eq "") { #khong thay
$secretword = "none";
}
print "Tu bi mat la gi?";
#
1.6.7 Giải quyết định dạng đầu vào thay đổi
Nếu đưa vào Jenny L Schwartz hay jenny thay vì Jenny thì chương trình sẽ không đưa
ra kết quả như mong đợi Vì toán tử so sánh eq thì lại so sánh đúng sự bằng nhau của từng ký tự một Ta hãy xem một cách giải quyết cho điều đó
Giả sử tôi muốn tìm bất kì xâu nào bắt đầu với Jenny, thay vì chỉ là một xâu bằng Jenny Tôi có thể làm điều này trong sed hay awk hoặc grep với một biểu thức chính qui-một tiêu bản sẽ xác định ra một tập hợp các xâu sánh đúng Giống như trong sed
Trang 9hay grep, biểu thức chính qui trong Perl để sánh bất kì xâu nào bắt đầu với Jenny là
^Jenny Để sánh xâu này với xâu trong $name, chúng ta dùng toán tử so sánh như sau:
Chú ý rằng biểu thức chính qui được định biên bởi dấu sổ chéo (/) Bên trong các dấu
sổ chéo thì các dấu cách và khoảng trắng là có nghĩa, hệt như chúng ở bên trong xâu.Nhưng chúng ta vẫn chưa giải quyết việc lựa chọn jenny hay loại bỏ Jennyy Để chấp nhận jenny, chúng ta thêm tuỳ chọn bỏ qua không xét chữ hoa và chữ thường, một chữ
i nhỏ được thêm vào sau dấu sổ chéo thứ hai Để loại bỏ Jennyy, ta thêm một đánh dấu đặc biệt định biên từ (tương tự với vi và một số bản của grep) dưới dạng \b trong biểu thức chính qui Điều này đảm bảo rằng kí tự đi sau chữ y đầu tiên trong biểu thức chính qui không phải là một kí tự a,b,c khác Điều này làm thay đổi biểu thức chính qui thành /^jenny\b/i, có nghĩa là "jenny tại đầu xâu, không có kí tự hay chữ số nào theo sau, và chấp nhận cả hai kiểu chữ hoa thường" Ta có chương trình như sau:
print "Xin chao ban $name!\n"; #chao thuong thuong
$secretword = $words{$name}; #lay tu bi mat
if ($secretword eq "") { #khong thay
while ($correct ne $secretword) {
print "Sai roi thu lai di TU bi mat la gi? ";
Trang 10$guess = <STDIN>;
chop($guess);
} #ket thuc cua while
}
Như bạn có thể thấy, chương trình này khác xa với chương trình đơn giản lúc đầu
nhưng nó vẫn còn rất nhỏ bé và làm việc được
Perl đưa ra tính năng về các biểu thức chính qui có trong mọi trình tiện ích UNIX chuẩn (và thậm chí trong một số không chuẩn mấy vẫn được) Không chỉ có thế, cách thức Perl giải quyết cho việc so sánh xâu là cách nhanh nhất trên hành tinh nay (Một chương trình giống như grep được viết trong Perl thường tốt hơn nhiều so với chương trình grep được các nhà cung cấp viết trong C với hầu hết các dữ liệu vào)
1.6.8 Làm cho công bằng với mọi người
Vậy bây giờ tôi có thể đưa vào Jenny hay jenny hoặc Jenny L Schwartz, nhưng với những người khác thì sao? Tom vẫn phải nói đúng là Tom (thậm chí không được có Tom với một dấu cách theo sau)
Để công bằng cho Tom (và cho những người khác), chúng ta cần nắm được từ đầu của bất kì cái gì được đưa vào, và chuyển nó thành chữ thường trước khi ta tra tên trong bảng Ta làm điều này bằng hai toán tử: toán tử thay thế sẽ tìm ra một biểu thức chính qui và thay thế nó bằng một xâu; và toán tử hoán chuyển để chuyển toàn bộ này thành chữ thường
Trước hết, toán tử thay thế: chúng ta muốn lấy nội dung của $name, tìm kí tự đầu tiên không là từ, và loại đi mọi thứ từ đây cho đến cuối xâu /\W.*/ là một biểu thức chính qui mà ta đang tìm kiếm: \W viết tắt cho kí tự không phải là từ (một cái gì đó không phải là chữ, chữ số hay gạch dưới) và * có nghĩa là bất kì kí tự nào từ đấy tới cuối dòng Bây giờ, để loại những kí tự này đi, ta cần lấy bất kì bộ phận nào của xâu sánh đúng với biểu thức chính qui này và thay nó với cái không có gì:
$name =~ s/\W.*//;
Chúng ta đang dùng cùng toán tử =~ mà ta đã dùng trước đó, nhưng bây giờ bên vế phải ta có toán tử thay thế: chữ s được theo sau bởi một biểu thức chính qui và xâu được định biên bởi dấu sổ chéo (Xâu trong thí dụ này là xâu rỗng giữa dấu sổ chéo thứ hai và thứ ba) Toán tử này trông giống và hành động rất giống như phép thay thế của các trình soạn thảo khác nhau
Bây giờ để có được bất kì cái gì còn lại trở thành chữ thường thì ta phải dịch xâu này dùng toán tử tr Nó trông rất giống chỉ lệnh tr của UNIX: nhận một danh sách các kí tự
để tìm và một danh sách các kí tự để thay thế chúng Với thí dụ của ta, để đặt nội dung của $name thành chữ thường, ta dùng:
$name =~ tr/A-Z/a-z/;
Các dấu sổ chéo phân cách các danh sách kí tự cần tìm và cần thay thế Dấu gạch ngang giữa A và Z thay thế cho tất cả các kí tự nằm giữa, cho nên chúng ta có hai danh sách, mỗi danh sách có 26 kí tự Khi toán tử tr tìm thấy một kí tự từ một xâu trong danh sách thứ nhất thì kí tự đó sẽ được thay thế bằng kí tự tương ứng trong danh sách thứ hai Cho nên tất cả các chữ hoa A trở thành chữ thường a, B trở thành b và cứ như thế
Trang 11$original_name = $name; #cat giu de chao mung
$name =~ s/\W.*//; #bo moi thu sau tu dau tien
$name =~ tr/A-Z/a-z/; #chuyen thanh chu thuong
if ($name eq "jenny") {
print "Chao Jenny! Lam bai tap di chu!\n";
} else {
print "Xin chao ban $original_name!\n"; #chao thuong thuong
$secretword = $words{$name}; #lay tu bi mat
if ($secretword eq "") { #khong thay
while ($correct ne $secretwords) {
print "Sai roi thu lai di Tu bi mat la gi? ";
Với chỉ vài câu lệnh, chúng ta đã tạo ra một chương trình thân thiện người dùng hơn nhiều Bạn sẽ thấy rằng việc diễn tả thao tác xâu phức tạp với vài cú gõ phím là một trong nhiều điểm mạnh của Perl
Tuy nhiên, vào tên để cho ta có thể so sánh nó và tra cứu nó trong bảng thì sẽ "phá huỷ" mất tên ta vừa đưa vào Cho nên, trước khi vào tên, cần phải cất giữ nó vào trong
Trang 12$original_name (Giống như các kí hiệu C, biến Perl bao gồm các chữ, chữ số và dấu gạch dưới và có thể có chiều dài gần như không giới hạn) Sau khi lưu giữ, ta có thể truy cập tới $original_name về sau.
Perl có nhiều cách để giám sát và cắt xâu bạn sẽ thấy phần lớn chúng trong Chương 7:Biểu thức chính qui và Chương 15:Việc chuyển đổi dữ liệu khác
1.6.9 Làm cho nó mô-đun hơn một chút
Bởi vì chúng ta đã thêm quá nhiều mã nên ta phải duyệt qua nhiều dòng chi tiết trước khi ta có thể thu được toàn bộ luồng chương trình Điều ta cần là tách logic mức cao (hỏi tên, chu trình dựa trên từ bí mật đưa vào) với các chi tiết (so sánh một từ bí mật với từ đã biết) Chúng ta có thể làm điều này cho rõ ràng, hoặc có thể bởi vì một người đang viết phần cao cấp còn người khác thì viết phần chi tiết
Perl cung cấp các chương trình con có tham biến và giá trị trả lại Một chương trình con được định nghĩa một lần ở đâu đó trong chương trình, và có thể được dùng lại nhiều lần ở những nơi khác trong chương trình
Với chương trình nhỏ nhưng phát triển nhanh của chúng ta (hihihi, đến mức chóng hết cả mặt rồi), ta hãy tạo ra một chương trình con tên là good_word sẽ nhận một tên
đã "sạch" và một từ đoán, rồi trả lại True nếu từ đó là đúng, và trả lại False nếu không đúng Chương trình con đó được viết kiểu như thế này (đại loại thế):
sub good_word {
my ($somename, $someguess) = @_; #lay cac tham so
$somename =~ s/\W.*//; #bo moi thu sau tu dau tien
$somename =~ tr/A-Z/a-z/; #chuyen thanh chu thuong
if ($somename eq "jenny") { #huh, Jenny, khong doan nua
return 1; #tra ve True
} elsif (($words{$somename} || "none") eq $someguess) {
return 1; #tra ve True
} else {
return 0; #tra ve False
}
} #ket thuc good_word
Trước hết, việc định nghĩa ra một chương trình con bao gồm một từ dành riêng sub đi theo sau là tên chương trình con tiếp nữa là một khối mã lệnh Định nghĩa này có thể
để vào bất kì đâu trong tệp chương trình, nhưng phần lớn mọi người thích để chúng vào cuối
Dòng đầu tiên trong định nghĩa đặc biệt này là một phép gán để lấy các giá trị của hai tham số của chương trình con này vào hai biến cục bộ có tên $somename và
$someguess (my() xác định hai biến là cục bộ cho chương trình con này, và các tham biến ban đầu trong một mảng cục bộ đặc biệt gọi là @_.)
Hai dòng tiếp làm sạch tên, cũng giống như bản trước của chương trình
Câu lệnh if-elsif-else quyết định xem từ được đoán ($someguess) là có đúng cho tên ($somename) hay không Nếu là Jenny thì thôi không kiểm tra nữa
Trang 13Biểu thức cuối cùng được tính trong chương trình con là để trả về giá trị Chúng ta sẽ thấy cách trả về lại giá trị được dùng sau khi kết thúc việc mô tả định nghĩa về chương trình con.
Phép kiểm tra cho phần elsif trông có phức tạp hơn một chút - ta hãy chia nó ra:
($words{$somename} || "none") eq $someguess
Phần thứ nhất bên trong dấu ngoặc là mảng băm quen thuộc, trả về một giá trị nào đó
từ %words dựa trên khoá $somename Toán tử đứng giữa giá trị đó và xâu "none" là toán tử || (phép logic hoặc) như được dùng trong C, awk và các kịch bản shell khác Nếu việc tra cứu từ mảng băm có một giá trị (nghĩa là khoá $somename tồn tại trong mảng), thì giá trị của biểu thức chính là là giá trị đó Nếu khoá không tìm được, thì xâu
"none" sẽ được dùng thay Đây chính là một cách kiểu Perl thường làm - xác định một biểu thức nào đó, và rồi đưa ra một giá trị mặc định bằng cách dùng || nếu biểu thức này có thể trở thành sai
Trong mọi trường hợp, dù đó là một giá trị từ mảng kết hợp, hay giá trị mặc định
"none", chúng ta đều so sánh nó với bất kì cái gì được đoán Nếu việc so sánh là đúng thì return 1, nếu không return 0
Và đây là chương trình hoàn chỉnh:
print "Xin chao ban $name!\n"; #chao thuong thuong
print "Tu bi mat la gi? ";
$guess = <STDIN>;
chop($guess);
while (! good_word($name, $guess)) {
print "Sai roi thu lai di Tu bi mat la gi? ";
Trang 14my ($somename, $someguess) = @_; #lay cac tham so
$somename =~ s/\W.*//; #bo moi thu sau tu dau tien
$somename =~ tr/A-Z/a-z/; #chuyen thanh chu thuong
if ($somename eq "jenny") { #huh, Jenny, khong doan nua
return 1; #tra ve True
} elsif (($words{$somename} || "none") eq $someguess) {
return 1; #tra ve True
} else {
return 0; #tra ve False
}
} #ket thuc good_word
Chú ý rằng chúng ta đã quay trở lại với biểu thức chính qui để kiểm tra Jenny, vì bây giờ không cần kéo một phần tên thứ nhất và chuyển nó thành chữ thường, chừng nào còn liên quan tới chương trình chính
Sự khác biệt lớn là chu trình while có chứa good_word Tại đây, chúng ta thấy một lời gọi tới chương trình con, truyền cho nó hai tham số $name và $guess Bên trong
chương trình con này, giá trị của $somename được đặt từ tham số thứ nhất, trong trường hợp này là $name; Giống thế, $someguess được đặt từ tham biến thứ hai,
$guess Giá trị do chương trình con trả vềi (hoặc 1 hoặc 0) được đảo ngược với toán tử tiền tố ! (phép phủ định logic not) Như trong C, toán tử này trả lại True nếu biểu thức
đi sau là False, và ngược lại Kết quả của phép phủ định này sẽ kiểm soát chu trình while Chú ý rằng chương trình con này giả thiết rằng giá trị của mảng %words được chương trình chính đặt
1.6.10 Chuyển danh sách từ bí mật vào tệp
Giả sử ta muốn dùng chung danh sách từ bí mật cho ba chương trình Nếu ta cất giữ danh sách từ như ta đã làm thì ta sẽ cần phải thay đổi tất cả ba chương trình này khi Tom quyết định rằng từ bí mật của mình sẽ là smallmouse thay vì bigmouse Điều này
có thể thành phiền phức, đặc biệt khi xem xét tới việc Tom lại thường xuyên thích thay đổi ý định (!!!!)
Cho nên, ta hãy đặt danh sách các từ vào một tệp, và rồi đọc tệp này để thu được danh sách từ vào trong chương trình Để làm điều này, ta cần tạo ra một kênh vào/ra được gọi là tước hiệu tệp Chương trình Perl của bạn sẽ tự động lấy ba tước hiệu tệp gọi là STDIN, STDOUT và STDERR, tương ứng với ba kênh vào ra chuẩn cho chương trình UNIX Chúng ta cũng đã dùng tước hiệu STDIN để đọc dữ liệu từ người chạy chương trình Bây giờ, đấy chỉ là việc lấy một tước hiệu khác để gắn với một tệp do ta tạo ra Sau đây là một đoạn mã nhỏ để làm điều đó:
Trang 15Định dạng được chọn bất kì cho danh sách từ là một khoản mục trên một dòng, với tên
và từ, luân phiên Cho nên, với cơ sở dữ liệu hiện tại của chúng ta, chúng ta có cái tựa như thế này:
Mặt khác, trường hợp thông thường là ở chỗ chúng ta đã đọc một dòng (kể cả dấu dòng mới) vào trong $name Trước hết, ta bỏ dấu xuống dòng bằng việc dùng hàm chop() Sau đó đọc dòng tiếp để lấy từ bí mật, giữ nó trong biến $word, nó một lần nữa cũng phải bỏ xuống dòng mới đi
Dòng cuối cùng của chu trình while đặt $word vào trong %words với khoá $name để phần còn lại của chương trình có thể truy nhập vào nó về sau
Một khi tệp đã được đọc xong thì có thể bỏ tước hiệu tệp bằng toán tử close()
Định nghĩa chương trình con này có thể đi sau hay trước chương trình con khác Và chúng ta gọi tới chương trình con thay vì đặt %words vào chỗ bắt đầu của chương trình, cho nên một cách để bao tất cả những điều này có thể giống thế này:
#!/usr/bin/perl
init_words();
print "Ten ban la gi? ";
Trang 16print "Xin chao ban $name!\n"; #chao thuong thuong
print "Tu bi mat la gi? ";
$guess = <STDIN>;
chop($guess);
while (! good_word($name, $guess)) {
print "Sai roi thu lai di Tu bi mat la gi? ";
my ($somename, $someguess) = @_; #lay cac tham so
$somename =~ s/\W.*//; #bo moi thu sau tu dau tien
$somename =~ tr/A-Z/a-z/; #chuyen thanh chu thuong
if ($somename eq "jenny") { #huh, Jenny, khong doan nua
return 1; #tra ve True
} elsif (($words{$somename} || "none") eq $someguess) {
return 1; #tra ve True
} #ket thuc init_words
Bây giờ nó đã bắt đầu trông giống một chương trình trưởng thành hoàn toàn Chú ý đến dòng thực hiện được đầu tiên là lời gọi tới init_words() Không có tham số nào được truyền cả, cho nên chúng ta được tự do bỏ đi dấu ( ) (tuy nhiên tôi không khuyên
Trang 17bạn làm điều này) Cũng vậy, giá trị trả về tử init_words không được dùng biểu thức nào hết, thì cũng là tốt vì ta đã không trả về điều gì đáng kể.
1.6.11 Đảm bảo một lượng an toàn (nhỏ thôi- nhưng an toàn vẫn hơn)
Xếp bạn quyết định: "Danh sách các từ bí mật phải thay đổi ít nhất một lần mỗi tuần" chúng ta không thể buộc danh sách này khác đi, nhưng chúng ta có thể ít nhất cũng đưa ra một cảnh báo nếu danh sách từ bí mật còn chưa được thay đổi trong hơn một tuần
Nơi tốt nhất để thực hiện việc kiểm tra là bên trong chương trình con init_words Toán
tử -M cho lại "tuổi" tính theo ngày từ một tệp hay tước hiệu tệp đã được thay đổi từ lần trước, cho nên ta chỉ cần xem liệu giá trị này có lớn hơn 7 hay không đối với tước hiệu tệp WORDSLIST:
Giá trị của -M WORDSLIST được so sánh với 7, và nếu lớn hơn, thế thì ta vi phạm điều
lệ an toàn rồi! Tại đây, ta thấy một toán tử mới: toán tử die die chỉ làm một công việc đơn giản là một thông báo lên thiết bị xuất và kết thúc chương trình
Phần còn lại của chương trình vẫn không đổi, cho nên tôi sẽ không lặp lại nó ở đây Bên cạnh việc lấy số ngày của tệp, ta cũng có thể tìm ra người chủ của nó, kích cỡ, thời gian thâm nhập, và mọi thứ khác mà UNIX duy trì về tệp Nhiều điều hơn được trình bầy trong Chương 10
1.6.12 Cảnh báo ai đó khi mọi việc đi sai
Ta hãy xem ta có thể làm cho hệ thống bị sa lầy thế nào khi ta gửi một mẩu thư điện tử mỗi lần cho một ai đó đoán từ bí mật của họ không đúng Ta cần sửa đổi mỗi chương trình con good_word vì ta có tất cả thông tin ngay đây Thư sẽ được gửi cho bạn nếu bạn cũng cấp địa chỉ email của mình vào chỗ "dia_chi_cua_ban_o_day" Đây là điều ta phải làm - ngay trước khi trả 0 về từ chương trình con, ta tạo ra một tước hiệu tệp mà thực tại là một tiến trình (mail), giống như:
sub good_word {
my ($somename, $someguess) = @_; #lay cac tham so
$somename =~ s/\W.*//; #bo moi thu sau tu dau tien
$somename =~ tr/A-Z/a-z/; #chuyen thanh chu thuong
Trang 18if ($somename eq "jenny") { #huh, Jenny, khong doan nua
return 1; #tra ve True
} elsif (($words{$somename} || "none") eq $someguess) {
return 1; #tra ve True
} else {
open(MAIL, "|mail dia_chi_cua_ban_o_day");
print MAIL "tin xau: $somename da doan $someguess\n”;
return 0; #tra ve False
}
} #ket thuc good_word
Câu lệnh mới thứ nhất ở đây là open(), mà có một kí hiệu | trong tên tệp Đây là một chỉ dẫn đặc biệt rằng ta đang mở một lệnh ngoài thay vì một tệp Vì nó nằm tại chỗ bắt đầu của lệnh ngoài nên có nghĩa là ta đang mở một lệnh ngoài để ta có thể ghi-xuất thông tin-lên nó (nếu bạn đặt | tại cuối thay vì đầu thì có nghĩa là bạn ở lệnh ngoài và đọc-thu tóm kết quả output của lệnh)
Câu lệnh tiếp, print, có một tước hiệu tệp (MAIL) giữa từ khoá print và giá trị được in cho ta biết là kết quả sẽ được ghi lên MAIL thay vì STDOUT
Cuối cùng, ta đóng tước hiệu tệp MAIL, cũng có nghĩa là kết thúc việc gởi dữ liệu ra lệnh ngoài
Perl có thể cũng gọi cả các lệnh với việc kiểm soát chính xác trên danh sách tham số,
mở các tước hiệu tệp, hay thậm chí lôi ra cả bản sao của chương trình hiện tại, và thực hiện hai (hay nhiều) bản sao song song Tất cả những điều này sẽ được mô tả trong Chương 14:Quản lí tiến trình
1.6.13 Nhiều tệp từ bí mật trong thư mục hiện tại
Ta hãy thay đổi định nghĩa của tên tệp từ bí mật một chút Thay vì tệp được đặt tên là wordslist, thì ta hãy tìm bất kì tệp nào trong thư mục hiện tại mà có tận cùng là secret Nếu dùng các kịch bản shell, bạn có thể sẽ dùng lệnh sau: echo *.secret để thu được một liệt kê ngắn gọn cho tất cả các tên này Như lát nữa bạn sẽ thấy, Perl dùng một cú pháp tên chùm tương tự Ta lấy lại định nghĩa init_words:
sub init_words {
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
Trang 19} #ket thuc while
} #ket thuc init_words
Trước hết, ta đã đưa ra một chu trình while mới trên phần lớn chương trình con của bản cũ Điều mới ở đây là toán tử <*.secret> Toán tử này sẽ trả về danh sách các tệp trong thư mục hiện thành có tên kết thúc bởi secret, và trong chu trình while đầu tiên, biến $filename sẽ lần lượt nhận các giá trị của danh sách các tệp này Khi không có thêm tên tệp nào thoã mãn điều kiện nữa được cho lại thì nó trả về xâu rỗng
Cho nên nếu thư mục hiện tại có chứa tom.secret và jerry.secret, thì $filename sẽ là jerry.secret ở bước đầu của chu trình while (tên tệp được xếp trật tự a,b,c ) Ở bước thứ hai, $filename là tom.secret Và vì không có tình huống thứ ba nên nó trả lại xâu rỗng khi lần thứ ba được gọi tới, làm cho điều kiện của chu trình while thành False, và
ta kết thúc chu trình while (đồng thời kết thúc luôn chương trình con)
Bên trong chu trình while phía trong, chúng ta mở tệp và kiểm chứng rằng nó còn đủ
"trẻ" hay không (ít hơn 7 ngày từ lần sửa đổi trước) Với những tệp thoã mãn điều kiện, chúng ta duyệt qua như trước
Chú ý rằng nếu không có tệp nào so sánh với *.secret và lại ít hơn 7 ngày thì chương trình con sẽ thoát mà không đặt bất kì từ bí mật nào vào mảng %words Điều đó có nghĩa là mọi người sẽ phải dùng từ "none" Thế cũng được! Nhưng trong thực tế thì bạn nên thêm một lệnh kiểm tra nữa xem mảng %words có chứa phần tử nào hay không Bạn hãy xem hàm keys() ở Chương 5: Mảng băm
1.6.14 Mặc dù vậy, chúng ta biết họ là ai!
Phần này đã được lược bỏ trong lần tải bản mới của quyển Learning Perl!
1.6.15 Liệt kê các từ bí mật
Bây giờ ta lại muốn có một báo cáo về tất cả những từ bí mật hiện đang dùng, và chúng cũ đến đâu Điều này cũng có thể làm được Trước hết, ta hãy lấy ra tất cả các
từ bí mật, bằng việc lấy một đoạn mã trong chương trình con init_words:
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
} #ket thuc while
Ở (*) ta biết 3 điều: tên của tệp (trong $filename), tên một ai đó (trong $name), và từ
bí mật của người đó (trong $word) Sau đây là chỗ để chúng ta dùng công cụ sinh báo
Trang 20cáo của Perl Ta định nghĩa một định dạng ở đâu đó trong chương trình (thông thường gần cuối, giống như chương trình con):
ba trường Dòng đi sau dòng định nghĩa trường bao giờ cũng là dòng giá trị trường Dòng giá trị cho một danh sách các biểu thức mà sẽ được tính khi định dạng này được dùng, và kết quả của những biểu thức đó sẽ được gắn vào trong các trường đã được xác định trên dòng trước đó Ta gọi định dạng này với toán tử write, như thế này:
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
bí mật
Uhh, mà chúng ta còn chưa có nhãn cho các cột, thế mới chán chứ-Ohh, nhưng mà điều đó thì cũng không khó lắm Ta chỉ cần thêm vào định dạng trên đầu trang, như sau:
format STDOUT_TOP =
Page @<<
$%
Trang 21Ten tep Ten Tu bi mat
Dòng thứ ba của định dạng này để trống Vì dòng này không chứa bất kì trường nào nên dòng sau nó không phải là dòng giá trị trường Dòng trống này được sao trực tiếp lên output, tạo ra một dòng trống giữa số trang và tiêu đề cột dưới
Hai dòng cuối của định dạng này cũng không chứa trường nào, cho nên chúng được sao trực tiếp ra output Do vậy định dạng này sinh ra bốn dòng, một trong đó có một phần bị thay đổi qua mỗi trang
Chỉ cần thêm định nghĩa này vào chương trình trước là nó làm việc Perl để ý đến định dạng đầu trang tự động Perl cũng có các trường được canh giữa hay căn lề phải, và hỗ trợ cho cả canh lề 2 bên Chúng ta sẽ nói kỹ về vấn đề này trong Chương 11: Định dạng
1.6.16 Làm "ấn tượng" cho danh sách từ cũ
Khi ta đọc qua các tệp *.secret trong thư mục hiện tại, ta có thể tìm thấy các tệp quá
cũ (ta thường bỏ qua những tệp này từ trước) Ta hãy đi thêm một bước nữa - ta sẽ đổi tên chúng thành *.secret.old để cho khi nhìn vào ta sẽ thấy ngay tệp những tệp nào quá cũ Sau đây là cách thức thể hiện cho chương trình con init_words với sửa đổi này:sub init_words {
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
Trang 22}
Bạn hãy chú đến phần else mới của việc kiểm tra tuổi Nếu tệp cũ hơn 7 ngày, thì nó sẽ được đổi tên bằng hàm rename() Toán tử này lấy hai tham số: đổi tệp có tên trong tham số thứ nhất thành tên trong tham số thứ hai Perl có một phạm vi đầy đủ các toán tử thao tác tệp - gần như bất kì cái gì bạn có thể thực hiện cho một tệp trong chương trình C, bạn cũng có thể làm từ Perl Vấn đề này sẽ được bàn ký hơn trong chương 17
1.6.17 Duy trì một cơ sở dữ liệu đoán đúng cuối cùng
Ta hãy giữ lại dấu vết khi nào việc đoán đúng gần nhất đã được thực hiện cho mỗi người dùng Một cấu trúc dữ liệu mà dường như mới thoáng nhìn thì có vẻ được là mảng băm Chẳng hạn, câu lệnh: $last_good{$name} = time; gán thời gian UNIX hiện tại (một số nguyên lớn hơn 700 triệu-số giây trôi qua kể từ 1/1/1970) cho một phần tử của %last_good mà có tên với khoá đó Qua thời gian, điều này sẽ dường như cho ta một cơ sở dữ liệu chỉ ra thời điểm gần nhất mà từ bí mật đã được đoán đúng cho từng người dùng đã gọi tới chương trình này
Nhưng, mảng lại không tồn tại giữa những lần gọi chương trình Mỗi lần chương trình này được gọi thì một mảng mới lại được hình thành, cho nên nhiều nhất thì ta tạo ra được mảng một phần tử và rồi lập tức lại quên mất nó khi chương trình kết thúc
Ta dùng hàm dbmopen() để ánh xạ một mảng băm vào một tệp trên đĩa (thực tế là một cặp tệp trên đĩa), còn được gọi là DBM-Database Management Nó được dùng như thế này:
dbmopen(%last_good, "lastdb", 0666);
$last_good($name) = time;
dbmclose(%last_good);
Câu lệnh đầu tiên thực hiện việc ánh xạ này, dùng các tên tệp đĩa lastdb.dir và
lastdb.pag (các tên này là tên thông thường cho DBM được gọi là lastdb) Các thuộc tính về tệp UNIX được dùng cho hai tệp này nếu các tệp đó phải được tạo ra (khi chúng chưa có sẵn) là 0666 0666 này có nghĩa là bất kì ai cũng có thể đọc hay ghi lên tệp (bạn xem thêm phần manual của lệnh chomod)
Câu lệnh thứ hai chỉ ra rằng chúng ta dùng mảng băm đã được ánh xạ này giống hệt như mảng băm thông thường Tuy nhiên, việc tạo ra hay cập nhật một phần tử của mảng sẽ tự động cập nhật tệp đĩa tạo nên DBM Điều này cho mảng băm có một cuộc sống trải bên ngoài lời gọi hiện thời của chương trình-một sự bền lâu của riêng nó.Câu lệnh thứ ba ngắt mảng băm ra khỏi DBM, giống hệt thao tác đóng tệp close().Bạn có thể chèn thêm ba câu lệnh này vào ngay đầu các định nghĩa chương trình con Mặc dầu các câu lệnh được chèn thêm này duy trì cơ sở dữ liệu là tốt (và thậm chí còn tạo ra nó trong lần ddầu), chúng ta vẫn không có cách nào để xem xét thông tin trong
đó Để làm điều đó, ta có thể tạo ra một chương trình chỏ tách biệt trông đại thể như thế này:
#!/usr/bin/perl
dbmopen(%last_good, "lastdb", 0666);
foreach $name (sort keys(%last_good) {
$when = $last_good{$name};
Trang 23$hours = (time - $when) / 3600; #tinh gio da qua
Hàm sort sắp xếp theo thứ tự bảng chữ (hệt như việc truyền một tệp văn bản qua lệnh sort) Điều này bảo đảm rằng danh sách được xử lí bởi câu lệnh foreach bao giờ cũng theo thứ tự bảng chữ cái
Thân của chu trình foreach nạp vào hai biến được dùng trong định dạng STDOUT, và rồi gọi tới định dạng đó Chú ý rằng chúng ta đoán ra tuổi của một phần tử bằng cách trừ thời gian UNIX đã cất giữ (trong mảng) từ thời gian hiện tại (như kết quả của time) và rồi chia cho 3600 (để chuyển từ giây sang giờ)
Perl cũng cung cấp những cách thức dễ dàng để tạo ra và duy trì các cơ sở dữ liệu hướng văn bản (như tệp mật hiệu) và cơ sở dữ liệu bản ghi chiều dài cố định (như cơ
sở dữ liệu "đăng nhập lần cuối" do chương trình login duy trì) Những cơ sở dữ liệu này
sẽ được mô tả trong Chương 17:Thao tác cơ sở dữ liệu người dùng
(Sưu tầm từ diễn đàn tin học)
Thiết kế bởi Phạm Nguyễn Bảo Nguyên
GC Com 2005
Perl chương 2
2.1 Dữ liệu vô hướng là gì?
Vô hướng là loại dữ liệu đơn giản nhất mà Perl thao tác Một vô hướng thì hoặc là một
số (giống như 4 hay 3.25e20) hay một xâu các kí tự (giống Xin chào hay Gettysburg Address) Mặc dầu bạn có thể nghĩ về số và xâu như những vật rất khác nhau, nhưng Perl dùng chúng gần như là giống nhau, cho nên chúng ta sẽ nghiên cứu cả hai
Trang 24Một giá trị vô hướng có thể được tác động bởi các toán tử (giống như phép cộng hay ghép), và kết quả trả về nói chung là một kết quả vô hướng Một giá trị vô hướng có thể được cất giữ vào trong một biến vô hướng Các biến vô hướng có thể được đọc từ tệp và thiết bị, và có thể được ghi thiết bị xuất.
2.2 Số
Mặc dầu kiểu vô vô hướng thì hoặc là một số hay một xâu, nhưng cũng vẫn có ích khi
ta nhìn vào các số và xâu tách biệt nhau trong một chốc lát Ta sẽ xét số trước rồi đến xâu
2.2.1 Tất cả các số đều có cùng định dạng bên trong
Như bạn sẽ thấy trong vài đoạn tiếp đây, bạn có thể xác định cả số nguyên (toàn bộ số, giống như 14 hay 342) và số dấu phẩy động (số thực với dấu chấm thập phân, như 3.14 hay 1.35*1025) Nhưng bên trong, Perl chỉ tính với các giá trị dấu phẩy động độ chính xác gấp đôi Điều này có nghĩa là không có giá trị nguyên bên trong Perl - một hằng nguyên trong chương trình được xử lí như giá trị dấu phẩy động tương đương Bạn có lẽ không để ý đến việc chuyển đổi (hay quan tâm nhiều), nhưng bạn nên dừng tìm kiếm phép toán nguyên (xem như ngược với các phép toán dấu phẩy động), vì không có phép tính nào như vậy đâu
2.2.2 Hằng kí hiệu động
Hằng kí hiệu là một cách để biểu diễn một giá trị trong văn bản chương trình Perl - bạn cũng có thể gọi điều này là một hằng trong chương trình mình, nhưng tôi sẽ dùng thuật ngữ hằng kí hiệu Hằng kí hiệu là cách thức biểu diễn dữ liệu trong mã chương trình gốc của chương trình bạn như cái vào cho trình biên dịch Perl (dữ liệu được đọc từ hay ghi lên các tệp đều được xử lí tương tự, nhưng không đồng nhất)
Perl chấp nhận tập hợp đầy đủ các hằng kí hiệu dấu phẩy động có sẵn cho người lập trình C Số có hay không có dấu chấm thập phân đều được phép (kể cả tiền tố cộng hay trừ tuỳ chọn), cũng như phần chỉ số mũ phụ thêm (kí pháp luỹ thừa) với cách viết
Bạn đừng bắt đầu một số bằng 0 (zero), vì Perl hỗ trợ cho hằng kí hiệu hệ cơ số tám và
hệ mười sáu (hệt như kiểu C) Số hệ tám bắt đầu bằng số 0 đứng đầu, còn số hệ mười sáu thì bắt đầu bằng 0x hay 0X Các chữ số hệ mười sáu từ A đến F (trong cả hai kiểu chữ hoa thường) đều biểu thị cho các giá trị số qui ước từ 10 đến 15 Chẳng hạn:
Trang 25Xâu ngắn nhất có thể được là xâu rỗng-không có kí tự nào Xâu dài nhất thì chiếm trọn
bộ nhớ máy tính (mặc dầu bạn sẽ chẳng thể nào làm gì nhiều với nó cả) Điều này phù hợp với nguyên lí "không có giới" mà Perl cung cấp cho bạn Các xâu điển hình là các dẫy in được gồm các chữ, số và dấu ngắt trong phạm vi ASCII 32 tới ASCII 126 Tuy nhiên, khả năng để có bất kì kí tự nào từ 0 tới 255 trong một xâu có nghĩa là bạn có thể tạo ra, 'nhòm' qua, và thao tác dữ liệu nhị phân thô như các xâu - một cái gì đó mà phần lớn các trình tiện ích UNIX khác sẽ gặp khó khăn lớn (chẳng hạn, bạn có thể 'vá lỗi' UNIX bằng việc đọc nó vào trong xâu Perl, tiến hành thay đổi, và ghi lại kết quả).Giống như số, xâu có thể biểu diễn hằng kí hiệu (cách thức bạn biểu diễn xâu trong chương trình Perl) Các xâu hằng kí hiệu có theo hai hướng: xâu nháy đơn và xâu nháy kép
2.3.1 Xâu dấu nháy đơn
Xâu dấu nháy đơn là một dẫy các kí tự được bao trong dấu nháy đơn (') Dấu nháy đơn không phải là một phần của bản thân xâu - chúng chỉ có đó để Perl xác định chỗ bắt đầu và kết thúc của xâu Bất kì kí tự nào nằm giữa các dấu nháy (kể cả dấu xuống dòng, nếu xâu vẫn còn tiếp tục sang dòng sau) đều là hợp pháp bên trong xâu Hai ngoại lệ: để lấy được một dấu nháy đơn trong một xâu có nháy đơn, bạn hãy đặt trước
nó một dấu sổ chéo ngược Và để lấy được dấu sổ chéo ngược trong một xâu có nháy đơn, bạn hãy đặt trước dấu sổ chéo ngược nột dấu sổ chéo ngược nữa Ví dụ:
'hello' #năm kí tự: h, e, l, l, o
'dont\'t' #năm kí tự: d, o, n, nháy đơn, t
'' #xâu rỗng (không có kí tự)
'silly\\me' #silly, theo sau là một sổ chéo ngược, sau là me
"hello\n' #hello theo sau là sổ chéo ngược và n
'hello
there' #hello, xuống dòng, there (toàn bộ 11 kí tự)
Chú ý rằng \n bên trong môt xâu có nháy đơn thì không được hiểu là dòng mới, nhưng nếu là hai kí tự sổ chéo ngược thì khác (chỉ khi sổ chéo ngược đi theo sau bởi một sổ chéo ngược khác hay một dấu nháy đơn thì mới mang nghĩa đặc biệt)
2.3.2 Xâu dấu nháy kép
Xâu dấu nháy kép hành động hệt như xâu trong C Một lần nữa, nó lại là dãy các kí tự, mặc dầu lần này được bao bởi dấu ngoặc kép Nhưng bây giờ dấu sổ chéo ngược lấy toàn bộ sức mạnh của nó để xác định các kí tự điều khiển nào đó, hay thậm chí bất kì
kí tự nào qua các biểu diễn hệ tám hay hệ mười sáu Đây là một số xâu dấu nháy kép:
"hello world\n" # hello world, và dấu xuống dòng
"new \177" # new, dấu cách và kí tự xoá (177 hệ tám)
"coke\tsprite" # coke, dấu tab, và sprite
Trang 26Dấu sổ chéo có thể đứng trước nhiều kí tự khác nhau để hàm ý những điều khác nhau (về điển hình nó được gọi là lối thoát sổ chéo) Danh sách đầy đủ của các lối thoát xâu nháy kép được cho trong Bảng 2-1.
Bảng 2-1 Dấu sổ chéo ngược trong xâu nháy kép
\0x7f ký tự ASCII ở hệ mười sáu (7f=delete)
\cC ký tự điều khiển (ở đây là Ctrl-C)
\\ dấu sổ chéo ngược
\" dấu nháy kép
\l ký tự tiếp theo sẽ chuyển thành chữ thường
\L tất cả các ký tự tiếp theo cho tới \E sẽ thành chữ thường
\u ký tự tiếp theo sẽ chuyển thành chữ hoa
\U tất cả các ký tự tiếp theo cho tới \E sẽ thành chữ hoa
\E kết thúc \L hay \U
Một tính năng khác của xâu nháy kép là ở chỗ chúng cho phép chen lẫn các biến, nghĩa
là một số tên biến nào đó bên trong xâu được thay thế bởi giá trị hiện tại của chúng khi xâu được dùng Chúng ta đã không được giới thiệu một cách chính thức là các biến trông như thế nào (ngoại trừ trong phần mở đầu trên), cho nên tôi sẽ quay lại vấn đề này sau
2.4 Toán tử
Một toán tử tạo ra một giá trị mới (kết quả) từ một hay nhiều giá trị khác (các toán hạng) Chẳng hạn, + là một toán tử vì nó nhận hai số (toán hạng, như 5 và 6), và tạo
ra một giá trị mới (11, kết quả)
Các toán tử và biểu thức của Perl nói chung đều là siêu tập của các toán tử đã có trong hầu hết các ngôn ngữ lập trình tựa ALGOL/Pascal, như C Một toán tử bao giờ cũng trông đợi các toán hạng số hay xâu (hay có thể là tổ hợp của cả hai) Nếu bạn cung cấp một toán hạng xâu ở chỗ đang cần tới một số, hay ngược lại, thì Perl sẽ tự động
chuyển toán hạng đó bằng việc dùng các qui tắc khá trực giác, mà sẽ được nêu chi tiết trong mục "Chuyển đổi giữa số và xâu" dưới đây
2.4.1 Toán tử cho số
Perl cung cấp các toán tử cộng, trừ, nhân, chia điển hình thông thường Chẳng hạn:
2 + 3 # 2 cộng 3, = 5
5.1 - 2.4 # 5.1 trừ 2.4, = 2.7
Trang 2723 = 8 (nếu kết quả không thể khớp trong số dấu phẩy động độ chính xác gấp đôi, như một số âm mà lại luỹ thừa theo số không nguyên, hay một số lớn lấy luỹ thừa theo
số lớn, thì bạn sẽ nhận được lỗi 'định mệnh' - fatal error)
Perl cũng hỗ trợ cho toán tử lấy đồng dư mô-đun như trong C Giá trị của biểu thức 10
% 3 là số dư khi lấy 10 chia cho 3, chính là 1 Cả hai giá trị trước khi tính toán đều được đưa về giá trị nguyên, cho nên 10.5 % 3.2 được tính là 10 % 3
Các toán tử so sánh logic là hệt như các toán tử có trong C (< <= == >= > !=), và việc so sánh hai giá trị về mặt số sẽ cho lại một giá trị đúng (True) hay sai (False) Chẳng hạn, 3 > 2 trả về True vì 3 lớn hơn 2, trong khi 5 != 5 trả về False Các định nghĩa về đúng và sai được nói tới về sau, nhưng với hiện tại, các bạn hãy nghĩ về giá trị cho lại giống như chúng ở trong C - 1 là True, còn 0 là False (các toán tử này sẽ được nói lại trong Bảng 2-2)
"hello" "world" # tương tự như "helloworld"
'hello wordl' "\n" # hệt như "hello world\n"
"jerry" " " "tom" # hệt như "jerry tom"
Chú ý rằng việc ghép nối phải được gọi tường minh tới toán tử , không giống awk là bạn đơn thuần chỉ việc đặt hai xâu gần nhau
Một tập các toán tử cho xâu khác là toán tử so sánh xâu Các toán tử này đều tựa FORTRAN, như lt thay cho bé hơn Các toán tử so sánh các giá trị ASCII của các kí tự của xâu theo cách thông thường Tập đầy đủ các toán tử so sánh (cho cả số và xâu) được nêu trong Bảng 2-2
Trang 28sánh như số thì 7 hiển nhiên bé hơn 30, nhưng nếu được so sánh theo xâu, thì xâu
"30" sẽ đứng trước xâu "7" (vì giá trị ASCII của 3 thì bé hơn giá trị ASCII của 7), và do
đó là bé hơn Cho nên, không giống awk, Perl đòi hỏi bạn xác định đúng kiểu so sánh, liệu đó là số hay xâu
Chú ý rằng các phép so sánh số và xâu về đại thể ngược với những điều xẩy ra cho chỉ lệnh test của UNIX, mà thường dùng kí hiệu -eq để so sánh số còn = để so sánh xâu.Vẫn còn một toán tử xâu khác là toán tử lặp lại xâu, bao gồm một kí tự chữ thường đơn giản x Toán tử này lấy toán hạng trái của nó (một xâu), và thực hiện nhiều việc ghép bản sao của xâu đó theo số lần do toán hạng bên phải chỉ ra (một số) Chẳng hạn:
Số đếm bản sao (toán hạng bên phải) trước hết sẽ bị chặt đi để cho giá trị nguyên (4.8 trở thành 4) trước khi được sử dụng Số đếm bản sao bé hơn một sẽ gây ra kết quả là xâu rỗng (chiều dài không)
2.4.3 Thứ tự ưu tiên và luật kết hợp của toán tử
Thứ tự ưu tiên của toán tử xác định ra cách giải quyết trường hợp không rõ ràng khi nào dùng toán tử nào trên ba toán hạng Chẳng hạn, trong biểu thức 2+3*4, ta sẽ thực hiện phép cộng trước hay phép nhân trước? Nếu ta làm phép cộng trước thì ta sẽ được 5*4, hay 20 Nhưng nếu ta làm phép nhân trước (như ta vẫn được dạ trong toán) thì ta được 2+12, hay 14 Perl chọn định nghĩa toán học thông thường, thực hiện nhân trước Bởi điều này, ta nói nhân có số ưu tiên cao hơn cộng
Bạn có thể phá đi trật tự theo số ưu tiên bằng việc dùng dấu ngoặc Cho nên nếu tôi thực sự muốn cộng trước khi nhân, thì tôi có thể viết (2+3)*4, cho 20 Cũng vậy, nếu tôi muốn biểu thị rằng phép nhân được thực hiện trước phép cộng, tôi có thể trang điểm thêm, nhưng chẳng để làm gì, một cặp dấu ngoặc trong 2+(3*4)
Trong khi số ưu tiên là trực giác cho phép cộng và nhân thì ta bắt đầu lao vào vấn đề thường hay phải đương đầu với, chẳng hạn, phân biệt thế nào đối với phép ghép xâu
và nâng lên luỹ thừa Cách đúng đắn để giải quyết điều này là tra cứu sơ đồ số thứ tự
ưu tiên toán tử của Perl, được nêu trong Bảng 2-3 (chú ý rằng một số các toán tử còn chưa được mô tả) Với những toán tử cũng có trong C, thì những toán tử đó có cùng số thứ tự ưu tiên như chúng có trong C (mà tôi chẳng bao giờ có thể nhớ được)
Bảng 2-3: Luật kết hợp và số ưu tiên của các toán tử (thấp nhất đến cao nhất)
Luật kết hợp Toán tử
Trái các toán tử về "danh sách" (mảng)
Trái > (gọi hàm)
- (không xét) ++ (tăng 1) hoặc (giảm 1)
Phải ** (luỹ thừa)
Trang 29Phải ! ~ \ + - (phủ định lôgic, phủ định bit, tham khảo biến (như pointer trong C), số dương, số âm)
Trái =~ !~ (sánh, không sánh)
Trái * / % x (nhân, chia, đồng dư, lặp chuỗi)
Trái + - (cộng, trừ, ghép xâu)
Trái << >> (dịch chuyển bit)
- các toán tử 1 toán hạng (như chomp)
- < > <= >= lt gt le ge
- == != <=> eq ne cmp
Trái & (phép lôgíc 'and' theo bit)
Trái | ^ (phép lôgíc 'or', 'xor' theo bit)
Trái && (phép lôgíc 'and')
Left || (phép lôgíc 'or')
- (toán tử phạm vi), (cấu tử danh sách)
Phải ?: (if-then-else, như trong C)
Phải = += -+ += (các phép gán)
Trái , => (ngăn cách bằng , và bằng =>)
- các toán tử về danh sách (array)
Phải not (phép lôgíc 'not')
Trái and (phép lôgíc 'and')
Trái or xor (phép lôgíc 'or', 'xor')
Trong sơ đồ này, bất kì toán tử đã cho nào đều có số ưu tiên lớn hơn các toán tử được liệt kê trên nó, và có số ưu tiên thấp hơn các toán tử được liệt kê dưới nó Các toán tử tại cùng mức ưu tiên được giải quyết theo luật kết hợp
Giống như với số ưu tiên, luật kết hợp giải quyết trật tự của các phép toán khi hai toán
tử có cùng mức ưu tiên cùng tác động trên ba toán hạng:
2 ** 3 ** 4 # 2 ** (3 ** 4), hay 2 ** 81, hay xấp xỉ 2.41e24
72 / 12 / 3 # (72 / 12) / 3, hay 6 / 3, hay 2
30 / 6 * 3 # (30/6)*3, hay 15
Trong trường hợp thứ nhất, toán tử ** có luật kết hợp phải, cho nên các dấu ngoặc được áp dụng từ bên phải So sánh với nó, các toán tử * và / có luật kết hợp trái, cho tập các dấu ngoặc bên trái
2.4.4 Chuyển đổi giữa số và xâu
Nếu một giá trị xâu được dùng như một toán hạng cho một toán tử số (chẳng hạn, +), thì Perl sẽ tự động chuyển xâu thành giá trị số tương đương, dường như nó đã được đưa vào như một giá trị dấu phẩy động Những chất liệu phi số đằng đuôi và khoảng trắng đằng đầu đều bị bỏ qua một cách yên lặng và lễ phép, cho nên " 123.45fred" (với dấu cách đứng trước) chuyển thành 123.45 với lời 'nhắc nhở nhẹ nhàng' Tại một cực điểm của điều này, một cái gì đó không phải là số tẹo nào sẽ được chuyển thành 0 mà không có báo trước (như xâu "jerry" được dùng như số)
Trang 30Giống vậy, nếu một giá trị số được cho khi đang cần tới một giá trị xâu (cho phép ghép xâu chẳng hạn), thì giá trị số sẽ được mở rộng thành bất kì xâu nào sẽ được in ra cho
số đó Chẳng hạn, nếu bạn muốn ghép nối x và theo sau là kết quả của 4 nhân với 5 thì bạn có thể làm đơn giản là: "X".(4*5) sẽ thành "X".20, hay "X20" (nhớ rằng các dấu ngoặc này buộc 4*5 phải được tính trước khi xem xét toán tử ghép nối xâu)
Nói cách khác, bạn không thực sự phải lo lắng gì về liệu bạn có một số hay một xâu (phần lớn thời gian) Perl thực hiện mọi chuyển đổi cho bạn
2.5 Biến vô hướng
Một biến là một tên gọi cho một chỗ chứa giữ được một hay nhiều giá trị Tên của biến
là không đổi trong toàn bộ chương trình, nhưng giá trị hay các giá trị được chứa trong biến đó về cơ bản thì lại thay đổi đi thay đổi lại trong suốt sự thực hiện chương trình.Một biến vô hướng thì giữ một giá trị vô hướng riêng (biểu thị cho một số, hay một xâu, hay cả hai) Các tên biến vô hướng bắt đầu với dấu $ và tiếp theo sau là một chữ, rồi thì có thể là nhiều chữ, số hay dấu gạch dưới Chữ hoa và chữ thường là phân biệt: biến $A là khác biến $a Và tất cả các chữ, số và gạch thấp đều có nghĩa, cho nên:
$a_very_long_variable_that_ends_in_1 là khác với
$a_very_long_variable_that_ends_in_2
Bạn nói chung nên chọn tên biến mang một nghĩa nào đó có liên quan tới giá trị của biến đó Chẳng hạn, $xyz123 có lẽ không mang tính mô tả nhiều lắm nhưng
$line_length thì lại có nghĩa
2.6 Các toán tử trên biến vô hướng
Phép toán thông dụng nhất trên biến vô hướng là phép gán, chính là cách đặt một giá trị cho một biến Toán tử gán của Perl là dấu bằng (giống như C hay FORTRAN), để tên biến bên vế trái và cho giá trị của biểu thức bên vế phải, kiểu như:
$a = 17; # cho $a giá trị 17
$b = $a + 3; # cho $b giá trị hiện tại của $a cộng với 3 (20)
$b = $b * 2; # cho $b giá trị của $b được nhân với 2 (40)
Chú ý rằng dòng cuối dùng biến $b hai lần: khi lấy được giá trị của nó (ở vế phải dấu
=), và khi xác định xem phải đặt biểu thức tính được vào đâu (ở vế trái của dấu =) Điều này là hợp lệ, an toàn và trong thực tế, khá thông dụng Trong thực tế, nó thông dụng đến mức chúng ta sẽ thấy trong vài phút đây là ta có thể viết điều này bằng việc dùng cách viết tắt qui ước
Bạn có thể đã chú ý rằng các biến vô hướng bao giờ cũng được tham khảo bằng dấu $ đứng trước Trong các kịch bản shell, bạn dùng $ để lấy một giá trị, nhưng để $ đứng một mình để gán một giá trị mới Trong awk hay C, bạn để cho $ đứng riêng hoàn toàn.Việc gán vô hướng có thể được dùng như một giá trị cũng như một phép toán, như trong C Nói cách khác, $a = 3 có một giá trị, cũng như $a+3 có một giá trị Giá trị chính là số được gán, cho nên giá trị của $a = 3 là 3 Mặc dầu điều này dường như có
vẻ kì lạ lúc thoáng nhìn, việc dùng một phép gán như một giá trị lại có ích nếu bạn muốn gán một giá trị trung gian trong một biểu thức cho một biến, hay nếu bạn muốn đơn giản sao cùng một giá trị cho một hay nhiều biến Chẳng hạn:
$b = 4 + ($a = 3); # gán 3 cho $a, rồi cộng kết quả đó với 4 đặt vào $b, được 7
$d = ($c = 5); # sao 5 vào $c, và rồi sao vào $d
Trang 31$d = $c = 5; # tương tự như trên, nhưng không có dấu ngoặc
2.6.1 Toán tử gán hai ngôi
Gần như tất cả các toán tử hai ngôi tính một giá trị đều có dạng phép gán hai ngôi tương ứng với dấu bằng có bổ sung thêm phần tử Chẳng hạn, hai dòng sau đây là tương đương:
$a = $a + 5; # không có toán tử gán hai ngôi
$a += 5; # có toán tử gán hai ngôi
$str = $str " "; # thêm dấu cách vào $str
$str = " "; # cũng điều ấy với toán tử gán
Gần như tất cả các toán tử hai ngôi đều hợp lệ theo cách này Chẳng hạn, toán tử nâng lên luỹ thừa của sẽ được viết là **= Cho nên, $a **= 3 có nghĩa là "nâng một số trong
$a lên luỹ thừa ba, rồi đặt kết quả trở lại $a" Giống như toán tử gán đơn, các toán tử này cũng có một giá trị: giá trị mới của biến Chẳng hạn:
$a = 3;
$b = ($a += 4); # $a và $b cả hai bây giờ đều là 7
Nhưng không may là trật tự tính toán của các toán hạng của toán tử hai ngôi lại không được xác định, cho nên một số biểu thức không thể nào được xác định hoàn toàn:
$a = 3;
$b = ($a += 2) * ($a -= 2); # Chương trình tồi: $b có thể là 15 hay 3
Nếu toán hạng bên phải của phép nhân được tính đầu tiên thì kết quả sẽ là 3 lần 1, hay
3 Tuy nhiên, nếu toán hạng bên trái được tính trước toán hạng bên phải, thì nó là 5 lần
3, hay 15
2.6.2 Tự tăng và tự giảm
Dường như cũng đã đủ dễ dàng để thêm 1 vào $a bằng việc nói $a += 1 Perl còn đi
xa hơn và thậm chí lại còn làm ngắn hơn cho điều này nữa Toán tử ++ (được gọi là toán tử tự tăng) cộng thêm một vào toán hạng của nó, và cho lại giá trị đã được tăng, giống như:
$a += 1; # dùng toán tử gán
++$a; # dùng tự tăng tiền tố
$d = 17;
$e = ++$d; # $e và $d bây giờ đều là 18
Tại đây, toán tử ++ được dùng như toán tử tiền tố; tức là, toán tử xuất hiện ở bên trái toán hạng của nó Phép tự tăng cũng có thể được dùng trong dạng hậu tố (nằm ở bên phải toán hạng của nó) Trong trường hợp này, kết quả của biểu thức này là giá trị của biến trước khi biến được tăng lên Chẳng hạn:
$c = 17;
Trang 32$d = $c++; # $d là 17, nhưng $c bây giờ là 18
Vì giá trị của toán hạng thay đổi nên toán hạng này phải là một biến vô hướng, không phải là biểu thức Bạn không thể nói ++16 để có được 17, mà cũng không thể nói ++($a+$b) (là cách nào đó để có được giá trị lớn hơn tổng của $a và $b một đơn vị).Toán tử tự giảm ( ) cũng tương tự như toán tử tự tăng, nhưng trừ đi một thay vì cộng với một Giống như toán tử tự tăng, toán tử tự giảm cũng có dạng tiền tố và hậu tố Chẳng hạn:
2.6.3 Toán tử chop() và chomp()
Một toán tử khác là chop() Toán tử tiền tố này nhận một đối bên trong các dấu ngoặc của nó - tên của một biến vô hướng - và bỏ đi kí tự cuối cùng từ giá trị xâu của biến
đó Chẳng hạn:
$x = "Xin chào mọi người";
chop($x); # $x bây giờ là "Xin chào mọi ngườ"
Lưu ý rằng giá trị của đối bị thay đổi ở đây, do đó cần phải có một biến vô hướng, thay
vì chỉ đơn giản là giá trị vô hướng Sẽ là vô nghĩa, chẳng hạn, để viết chop('suey') để biến nó thành 'sue'’, vì không có chỗ nào để cất giữ giá trị này Bên cạnh đó, bạn có thể chỉ viết 'sue' cũng đủ
Toán tử này trông giống như một lời gọi hàm, và quả thực trả về một giá trị (mà bạn sẽ trông đợi nếu bạn quen thuộc với lời gọi hàm từ các ngôn ngữ) Giá trị được cho lại chính là kí tự đã bị loại bỏ (chữ i trong người ở trên) Điều này có nghĩa là đoạn mã sau đây có lẽ sai:
$x = chop($x); # SAI: thay thế $x bằng ksi tự cuối cùng của nó
chop($x); # Đúng: như trên, loại bỏ kí tự cuối
Nếu chop() được cho một xâu rỗng, thì nó chẳng làm gì cả
chomp() tương tự như chop() nhưng nó chỉ xoá nếu ký tự cuối dùng của biến là ký tự dòng mới mà thôi, nếu là ký tự khác thì nó không làm gì cả Bạn nên dùng chomp() thay cho chop() khi có thể vì nó an toàn hơn
2.6.4 Xen lẫn vô hướng vào trong xâu
Khi một hằng kí tự xâu là được đặt trong nháy kép thì nó là chủ đề cho việc xen lẫn biến (bên cạnh việc được kiểm tra cho lối thoát sổ chéo ngược) Điều này có nghĩa là xâu này được duyệt qua để tìm các tên biến vô hướng có thể - có nghĩa là dấu $ đi theo sau một chữ, số hay dấu gạch dưới Khi tìm thấy một tham khảo biến thì nó được thay thế bằng giá trị hiện tại (hay bất kì xâu rỗng nào nếu biến vô hướng còn chưa được gán giá trị nào) Chẳng hạn:
$a = "jerry";
$b = "some text $a"; # $b bây giờ là "some text jerry"
$c = "no such variable $what"; # $c là "no such variable "
Trang 33Để ngăn việc thay thế một biến bằng giá trị của nó, bạn phải hoặc làm thay đổi phần
đó của xâu để cho nó xuất hiện trong ngoặc đơn, hoặc đặt trước dấu $ một dấu sổ chéo ngược:
$jerry = 'hi';
$tom = "a test of ".'$jerry'; # hằng kí hiệu: ‘a test of $jerry’
$tom2 = "a test of \$jerry"; # cũng như vậy
Tên biến sẽ là tên biến dài nhất có thể mà tạo nên nghĩa tại phần đó của xâu Điều này
có thể là vấn đề nếu bạn muốn đặt sau ngay giá trị được thay thế với một văn bản hằng mà bắt đầu bằng một chữ, số hay dấu gạch thấp Vì Perl duyệt qua các tên biến nên nó sẽ xét những kí tự là các kí tự tên phụ, mà không phải là điều bạn muốn Perl cung cấp một định biên cho tên biến theo các hệ thống tương tự như lớp vỏ Bạn hãy đơn thuần bao tên của biến đó trong một cặp dấu { và } Hay bạn có thể kết thúc phần
đó của xâu và bắt đầu một phần khác của xâu bằng toán tử ghép nối:
$fred = 'pay'; $fredday = "wrong!";
$tom = "It’s $fredday"; # không phải payday, mà là "It’s wrong!"
$tom = "It’s ${fred}day"; # bây giờ, $tom là "It’s payday!"
$barney2 = "It’s $fred".'day'; # cách khác để làm việc đó
$barney3 = "It’s " $fred "day"; # và một cách khác nữa
Toán tử sổ chéo ngược chuyển hoa thường có thể được dùng để làm thay đổi chữ hoa thường được đem theo cùng việc xen lẫn biến Chẳng hạn:
$bigfred = "\Ufred"; # $bigfred là FRED
$jerry = "fred"; $bigfred = "\U$jerry"; # tương tự như trên
$capfred = "\u$jerry"; # $capfred là "Fred"
$tom = "\LBARNEY"; # $tom bây giờ là "barney"
$capbarney = "\u\LBARNEY"; # capbarney bây giờ là "Barney"
$bigbarney = "BARNEY"; $capbarney = "\u\L$bigbarney"; # như trên
Như bạn có thể thấy, các toán tử dịch chuyển hoa thường được ghi nhớ bên trong xâu chừng nào chúng còn chưa được dùng tới, cho nên ngay kí tự đầu tiên của $capbarney không tuân theo \u, nó vẫn còn là chữ hoa vì \u chưa được dùng đến cho đến khi \L được dùng xong
Thuật ngữ xen lẫn biến thường được dùng lẫn với xen lẫn nháy kép, vì các xâu có nháy kép là chủ đề cho việc xen lẫn biến
2.7 <STDIN> xem như một vô hướng
Tại điểm này, bạn có thể tự hỏi làm sao lấy được một giá trị vào trong chương trình Perl Sau đây là cách đơn giản nhất Mỗi lần bạn dùng <STDIN> ở chỗ đang trông đợi một giá trị vô hướng, thì Perl sẽ đọc toàn bộ dòng văn bản tiếp từ lối vào chuẩn (cho tới dấu dòng mới đầu tiên), và dùng xâu đó như giá trị cho <STDIN> Đầu vào chuẩn
có thể mang nhiều nghĩa, nhưng chừng nào bạn còn chưa làm điều gì đó thì nó vẫn còn mang nghĩa là thiết bị cuối của người dùng, người đã gọi chương trình của bạn (có thể
là bạn) Nếu không có gì chờ đợi để đọc cả (trường hợp điển hình, chừng nào bạn còn chưa gõ xong toàn bộ dòng), thì chương trình Perl sẽ dừng và đợi cho bạn đưa vào một
số kí tự theo sau bằng một dấu dòng mới (xuống dòng)
Trang 34Giá trị xâu của <STDIN> về điển hình có một dấu dòng mới ở cuối của nó Thông thường nhất là bạn muốn gỡ bỏ cái dấu dòng mới đó đi (có sự khác biệt lớn giữa hello
và hello\n) (dùng toán tử chop() để hỗ trợ) Một dãy data input đại loại như thế này:
$a = <STDIN>; # nhận văn bản
chomp($a); # gỡ bỏ dấu dòng mới
Cách viết tắt thông dụng cho hai dòng này là:
chomp($a = <STDIN>);
Phép gán bên trong các dấu ngoặc tròn tiếp tục là một tham khảo tới $a, thậm chí sau khi nó đã được trao cho một giá trị với toán tử <STDIN> Vậy, toán tử chomp() làm việc trên $a (Điều này là đúng nói chung đối với toán tử gán - một biểu thức gán có thể được dùng bất kì khi nào một biến là cần tới, và những hành động tham khảo tới biến đó ở bên trái của dấu bằng)
print "Xin chào mọi người\n"; # tương tự như trên
Lưu ý rằng thí dụ thứ hai chỉ ra dạng của print() không có dấu ngoặc Trong thực tế, nhiều toán tử trông như các hàm cũng có dạng cú pháp làm việc không cần dấu ngoặc
Dù có dùng hay không, dấu ngoặc cũng gần như là vấn đề về kiểu cách và sự nhanh nhẩu trong cách gõ, mặc dầu có vài trường hợp bạn sẽ cần các dấu ngoặc để loại bỏ bớt sự mập mờ
Chúng ta sẽ thấy rằng bạn thực tế có thể cho print một danh sách các giá trị, trong mục "Dùng print để xuất" ở Chương 6: Nhấp/Xuất căn bản, nhưng chúng ta vẫn còn chưa nói về danh sách, cho nên chúng ta sẽ nói nó về sau
2.9 Giá trị undef
Điều gì sẽ xảy ra nếu bạn dùng một biến vô hướng trước khi bạn cho nó một giá trị? Chẳng có gì nghiêm trọng cả Các biến đều có giá trị undef trước khi chúng được gán lần đầu tiên Giá trị này trông như số 0 khi được dùng như một số, hay xâu rỗng chiều dài 0 nếu được dùng như một xâu Nhiều toán tử trả về undef khi các đối vượt ra ngoài phạm vi và thành vô nghĩa
Một toán tử mà chúng ta đã thấy có trả về undef là toán tử <STDIN> Thông thường toán tử này cho lại một xâu của dòng tiếp vừa được đọc, tuy nhiên (như khi bạn gõ control-D tại thiết bị cuối, hay khi một tệp không còn dữ liệu nữa), thì toán tử này trả vềi undef như một giá trị Chúng ta sẽ thấy trong chương 6 cách kiểm tra này và chọn hành động đặc biệt khi không còn dữ liệu nào có sẵn để đọc nữa
2.10 Bài tập
Bạn hãy viết 1 chương trình tính và in ra chu vi của 1 đường tròn cho bán kính là 12.5 Hãy sửa lại chương trình ở trên sao cho chương trình đưa ra 1 câu thông báo và đợi người dùng nhập bán kính vào từ bàn phím
Trang 35Bạn hãy viết chương trình đọc vào 2 số và in ra tích của 2 số đó
Bạn hãy viết chương trình đọc vào một xâu và một số, sau đó in ra xâu đó với số lần được chỉ định bởi giá trị của số được nhập vào (dùng toán tử x)
(Sưu tầm từ diễn đàn tin học)
Thiết kế bởi Phạm Nguyễn Bảo Nguyên
3.2 Biểu diễn hằng kí hiệu
Một hằng kí hiệu mảng (cách thức bạn biểu diễn giá trị của một mảng bên trong
chương trình) là một danh sách các giá trị tách nhau bằng dấu phẩy và được bao trong dấu ngoặc tròn Những giá trị này tạo nên các phần tử của danh sách Chẳng hạn:(1,2,3) # mảng gồm ba giá trị 1, 2 và 3
("jerry", 4.5) # hai giá trị, "jerry" và 4.5
Các phần tử của mảng không nhất thiết là hằng - chúng có thể là biểu thức mà sẽ được tính mới lại mỗi lần mảng được sử dụng Chẳng hạn:
($a, 17) # hai giá trị: giá trị hiện tại của $a, và 17
($b+$c,$d+$e) # hai giá trị
Mảng rỗng (mảng không có phần tử nào) được biểu diễn bằng một cặp dấu ngoặc rỗng:
() # mảng rỗng (không phần tử)
Một phần tử của mảng có thể bao gồm toán tử cấu thành mảng, được chỉ ra bởi hai giá trị vô hướng tách nhau bởi hai dấu chấm liên tiếp ( ) Toán tử này tạo ra một danh sách các giá trị bắt đầu tại giá trị vô hướng bên trái kéo cho tới gía trị vô hướng bên phải, mỗi lần tăng lên một Chẳng hạn:
(1 5) # giống như (1, 2, ,3 ,4, 5)
(1.2 5.2) # giống như (1.2, 2.2, 3.2, 4.2, 5.2)
(2 6,10,12) # giống như (2,3,4,5,6,10,12)
($a $b) # phạm vi được xác định bởi giá trị hiện tại của $a và $b
Nếu giá trị vô hướng bên phải bé hơn vô hướng bên trái thì sẽ tạo ra danh sách rỗng - bạn không thể đếm ngược trật tự của các giá trị Nếu giá trị cuối cùng không phải là toàn bộ số bước trên giá trị ban đầu thì danh sách sẽ dừng chỉ ngay trước giá trị tiếp
mà sẽ vượt ra ngoài phạm vi:
(1.3 6.1) # giống như (1.3, 2.3, 3.3, 4.3, 5.3)
Trang 36Một cách dùng của hằng kí hiệu mảng là như tham số của toán tử print() đã được giới thiệu trước đây Các phần tử của danh sách này được in ra mà không có bất kì khoảng trống xen thêm vào:
print ("Câu trả lời là ", $a, "\n");
Câu lệnh này in ra Câu trả lời là, theo sau bởi một dấu cách, giá trị của $a, và xuống dòng mới Ta hãy chuyển sang cách dùng khác cho hằng kí hiệu mảng
3.3 Biến mảng
Một biến mảng giữ một giá trị mảng riêng Các tên biến mảng là tương tự với các tên biến vô hướng, chỉ khác kí tự khởi đầu, là một dấu @ chứ không phải là dấu $ Chẳng hạn:
@jerry # biến mảng @jerry
@A_Very_Long_Array_Variable_Name
@A_Very_Long_Array_Variable_Name_that_is_different
Lưu ý rằng biến mảng @jerry là không có quan hệ gì theo bất kì cách nào với biến vô hướng $jerry Perl duy trì không gian tên tách biệt cho các kiểu đối tượng khác nhau.Giá trị của một biến mảng mà chưa được gán là (), danh sách rỗng
Một biểu thức có thể tham khảo tới các biến mảng như một tổng thể, hoặc nó có thể xem xét và thay đổi từng phần tử của mảng đó
3.4 Các hàm và phép toán trên mảng
Các toán tử mảng hành động trên các mảng như một tổng thể Một số toán tử mảng có thể cho lại một giá trị mảng, mà có thể hoặc được dùng như một giá trị cho toán tử mảng khác, hoặc được gán vào một biến mảng khác
@tom = @jerry; # bây giờ được sao sang @tom
Nếu một giá trị vô hướng được gán vào trong một biến mảng thì giá trị vô hướng trở thành phần tử duy nhất của mảng:
@huh = 1; # 1 được đặt chuyển thành (1) và gán cho @huh
Tên biến mảng có thể xuất hiện trong danh sách hằng kí hiệu mảng Khi giá trị của danh sách được tính thì Perl thay thế tên biến mảng bằng giá trị hiện tại của mảng đó, giống vậy:
@jerry = ("một", "hai");
@tom = (4,5,@jerry, 6, 7); # @tom trở thành (4,5,"một","hai",6,7)
@tom = (8, @tom); # đặt 8 vào trước @tom
@tom = (@tom, “cuối”); # và "cuối" vào phía sau @tom
# @tom bây giờ là (8,4,5,"một","hai",6,7,"cuối")
Lưu ý rằng các phần tử mảng được thêm vào đều ở cùng mức như phần còn lại của hằng kí hiệu - một danh sách không thể chứa một danh sách khác như một phần tử
Trang 37Nếu một mảng hằng kí hiệu chỉ chứa các tham khảo biến (không phải là biểu thức) thì mảng hằng kí hiệu ấy cũng có thể được xử lí như một biến Nói cách khác, một mảng hằng kí hiệu như thế có thể được dùng ở vế bên trái của phép gán Mỗi biến vô hướng trong mảng kí hiệu nhận một giá trị tương ứng từ danh sách ở vế phải của phép gán Chẳng hạn:
($a, $b, $c) = (1, 2, 3); # gán 1 cho $a, 2 cho $b, 3 cho $c
($a, $b) = ($b, $a); # tráo đổi $a và $b
($d, @jerry) = ($a, $b, $c); # gán $a cho $d, và ($b,$c) cho @jerry
($e, @jerry) = @jerry; # loại bỏ phần tử thứ nhất của @jerry là $e
# điều này làm cho @jerry = ($c) và $e = $b
Nếu số phần tử được gán không sánh đúng với số các biến để giữ các giá trị thì mọi giá trị vượt quá (vế phải) đều bị loại bỏ, và bất kì biến vượt quá nào (ở vế trái của dấu bằng) đều được cho giá trị undef
Một biến mảng xuất hiện trong danh sách mảng hằng kí hiệu đều phải ở cuối, vì biến mảng nó tiêu thụ tất cả các giá trị còn lại (bạn có thể đặt các biến khác sau nó, nhưng chúng sẽ chỉ nhận giá trị undef mà thôi)
Nếu một biến mảng được gán cho một biến vô hướng thì số được gán là chiều dài của mảng, như trong:
@jerry = (4, 5, 6); # khởi đầu @jerry mang giá trị (4,5,6)
$a = @jerry; # $a = 3, là số lượng các phần tử có trong @jerry
Chiều dài cũng được cho lại nếu một tên biến mảng được dùng trong hầu hết mọi chỗ
mà một giá trị vô hướng đang được cần tới (Trong mục dưới đây có tên "Hoàn cảnh vô hướng mảng", chúng ta sẽ thấy rằng điều này quả là được gọi như vậy với việc dùng tên mảng trong hoàn cảnh vô hướng) Chẳng hạn, để lấy giá trị bé hơn chiều dài mảng một đơn vị, bạn có thể dùng @jerry-1, vì toán tử trừ vô hướng cần các vô hướng cho
cả hai toán hạng của nó Chú ý điều sau:
@jerry = (4, 5, 6); # khởi đầu @jerry mang giá trị (1,2,3)
$a = @jerry; # $a nhận chiều dài của @jerry (là 3)
($a) = @jerry; # $a nhận phần tử đầu tiên của @jerry (là 4)
Phép gán $a = đầu tiên là phép gán vô hướng, và do vậy @jerry được xem như một vô hướng, cho lại chiều dài của nó Phép gán thứ hai là phép gán mảng (cho dù chỉ một giá trị là cần tới), và do vậy là phần tử đầu tiên của @jerry, bỏ đi tất cả phần còn lại.Giá trị của phép gán mảng là chính bản thân giá trị mảng, và có thể được xếp tầng như bạn có thể làm với các phép gán vô hướng Chẳng hạn:
@jerry = (@tom = (2,3,4)); # @jerry và @tom mang giá trị (2,3,4)
@jerry = @tom = (2,3,4); # tương tự như trên
3.4.2 Truy cập các phần tử của mảng
Cho tới nay, chúng ta vẫn xử lí mảng như một tổng thể, thêm vào và bỏ bớt các giá trị bằng việc thực hiện gán mảng Nhiều chương trình có ích đã được xây dựng dùng mảng mà thậm chí chẳng truy cập vào phần tử mảng nào Tuy nhiên, Perl cung cấp toán tử chỉ số truyền thống để tham khảo tới một phần tử mảng theo chỉ số Với toán
tử chỉ số mảng, các phần tử mảng đều được đánh số bằng việc dùng số nguyên tuần
Trang 38tự, bắt đầu từ không và tăng lên một cho mỗi phần tử Phần tử đầu tiên của mảng
@jerry mà được truy cập tới là $jerry[0] Chú ý rằng @ trên tên mảng trở thành $ trên tham khảo phần tử Đó là vì việc tham khảo tới một phần tử của mảng xác định ra một biến vô hướng (một phần của mảng), mà có thể hoặc được gán cho, hoặc có giá trị hiện tại của nó được dùng trong một biểu thức, kiểu như:
@jerry = (7,8,9);
$b = $jerry[0]; # đặt 7 vào $b (phần tử đầu tiên của @jerry)
$jerry[0] = 5; # bây giờ @jerry = (5,8,9)
Cũng có thể truy cập tới các phần tử khác cũng dễ tương tự, như trong:
$c = $jerry[1]; # gán $jerry[1] vào $s, lúc này $c = 8
$jerry[2]++; # tăng 1 cho phần tử thứ ba của @jerry
$jerry[1] += 4; # cộng 4 vào phần tử thứ hai
($jerry[0], $jerry[1]) = ($jerry[1], $jerry[0]); # tráo đổi hai phần tử đầu của @jerryViệc truy cập vào một danh sách các phần tử từ cùng mảng (như trong thí dụ cuối) được gọi là lát cắt, và thường xuất hiện đến mức có một cách biểu diễn đặc biệt cho nó:
@jerry[0,1] # tương tự như ($jerry[0], $jerry[1])
@jerry[0,1] = @jerry[1,0]; # tráo đổi hai phần tử đầu
@jerry[0,1,2] = @jerry[1,1,1]; # làm cho cả 3 phần tử đầu tiên giống phần tử thứ hai
@jerry[1,2] = (9,10); # đổi hai giá trị cuối thành 9 và 10
Chú ý rằng lát cắt này dùng tiền tố @ chứ không là $ Điều này là vì bạn đang tạo ra một biến mảng bằng việc chọn một phần của mảng chứ không phải là biến vô hướng chỉ thâm nhập vào một phần tử
Lát cắt cũng làm việc trên danh sách hằng kí hiệu, hay bất kì toán tử nào cho lại một giá trị danh sách:
@who = ("jerry","tom","chip","dale")[2,3]; # @who = ("chip", "dale")
Các giá trị chỉ số trong những thí dụ này là các số nguyên hằng kí hiệu, nhưng chỉ số cũng có thể là bất kì biểu thức nào cho lại một số, mà rồi được dùng để chọn phần tử thích hợp:
@jerry = (7,8,9);
$a = 2;
$b = $jerry[$a]; # $b = $jerry[2], hay giá trị 9
$c = $jerry[$a-1]; # $c nhận $jerry[1], hay 8
($c) = (7,8,9) [$a-1]; # tương tự như trên, nhưng dùng lát cắt
Vậy chương trình Perl có thể có việc thâm nhập mảng tương tự như các ngôn ngữ lập trình truyền thống Ý tưởng này về việc dùng một biểu thức cho chỉ số cũng có tác dụng cho các lát cắt Tuy nhiên bạn hãy nhớ rằng chỉ số cho lát cắt là một danh sách các giá trị, cho nên biểu thức này là một biểu thức mảng, thay vì là một biểu thức vô hướng
@jerry = (7,8,9); # như trong thí dụ trước
@tom = (2,1,0);
Trang 39@backfred = @jerry[@tom]; # giống như @jerry[2,1,0], hay ($jerry[2],$jerry[1],
$jerry[0]), hay (9,8,7)
Nếu bạn thâm nhập vào một phần tử mảng bên ngoài hai đầu của mảng hiện tại (tức là một chỉ số bé hơn không hay lớn hơn chỉ số của phần tử cuối cùng), thì giá trị undef sẽ được trả về mà không có lời cảnh báo Chẳng hạn:
@jerry = (1,2,3);
$tom = $jerry[7]; # $tom bây giờ là undef
Việc gán một giá trị bên ngoài đầu của mảng hiện tại sẽ tự động mở rộng mảng (cho một giá trị undef cho tất cả các giá trị trung gian, nếu có) Chẳng hạn:
@jerry = (1,2,3);
$jerry[3] = "hi"; # @jerry bây giờ là (1,2,3,"h"”)
$jerry[6] = "ho"; # @jerry bây giờ là (1,2,3,"hi",undef,undef,"ho")
Bạn có thể dùng $#jerry để lấy giá trị chỉ số của phần tử cuối của @jerry (tương tự như trong C-Shell) Bạn thậm chí còn có thể gán vào trong giá trị này để làm thay đổi chiều dài hiển nhiên của @jerry, làm cho nó to lên hay co lại, nhưng điều đó nói chung là không cần thiết, vì mảng thường to lên hay co lại một cách tự động
3.4.3 Các toán tử push() và pop()
Một cách dùng thông dụng của mảng là như một chồng thông tin, nơi những giá trị mới được thêm vào và lấy đi từ phía bên phải của danh sách Những phép toán này thường xuất hiện đến mức chúng có các hàm đặc biệt của riêng chúng:
push(@mylist,$newvalue); # giống @mylist = (@mylist, $newvalue)
$oldvalue = pop(@mylist); # lấy ra phần tử cuối của @mylist
Toán tử pop() trả về undef nếu tham số của nó là một danh sách rỗng
Toán tử push() cũng chấp nhận một danh sách các giá trị cần được đẩy vào danh sách Các giá trị được đẩy vào cuối của danh sách Chẳng hạn:
@mylist = (1,2,3);
push(@mylist,4,5,6); # @mylist = (1,2,3,4,5,6)
Chú ý rằng đối thứ nhất phải là một tên biến mảng - đẩy vào và lấy ra sẽ không có ý nghĩa với danh sách hằng kí hiệu
3.4.4 Các toán tử shift() và unshift()
Các toán tử push() và pop() làm việc ở bên "phải" của danh sách (phần với chỉ số cao nhất) Tương tự thế, các toán tử unshift() và shift() thực hiện những hành động tương ứng về bên "trái" của một danh sách (phần với chỉ số thấp nhất) Sau đây là vài thí dụ:unshift(@jerry,$a); # như @jerry = ($a,@jerry);
unshift(@jerry,$a,$b,$c); # như @jerry = ($a, $b, $c, @jerry);
$x = shift(@jerry); # như ($x,@jerry) = @jerry;
@jerry = (5,6,7);
unshift(@jerry,2,3,4); # @jerry bây giờ là (2,3,4,5,6,7)
$x = shift(@jerry); # $x nhận 2, $jerry nhận bây giờ là (3,4,5,6,7)
Tương tự như pop(), shift() sẽ trả về undef nếu tham số của nó là một mảng rỗng.3.4.5 Toán tử reverse()
Trang 40Toán tử reverse() đảo ngược trật tự các phần tử của tham số của nó, cho lại danh sách kết quả Chẳng hạn:
@a = (7,8,9);
@b = reverse(@a); # đặt $b giá trị (9,8,7)
$b = reverse(7,8,9); # tương tự như trên
Chú ý rằng danh sách đối là không bị thay đổi - toán tử reverse() chỉ làm việc trên bản sao Nếu bạn muốn đảo ngược một mảng "tại chỗ", thì bạn cần gán nó ngược trở lại cho cùng biến:
@b = reverse(@b); # đặt @b là đảo ngược của chính nó
3.4.6 Toán tử sort()
Toán tử sort() nhận một danh sách làm tham số, sắp xếp thứ tự danh sách theo thứ tự ASCII và trả về kết quả sau khi đã sắp xếp tăng dần sort() không làm thay đổi danh sách gốc Chẳng hạn:
@x = sort("small", "medium", "large"); # @x nhận "large", "medium", "small"
@y = (1,2,4,8,16,32,64);
@y = sort(@y); # @y nhận 1, 16, 2, 32, 4, 64, 8
Chú ý rằng các số sắp xếp không xuất hiện theo thứ tự số, nhưng theo giá trị xâu của từng số (1, 16, 2, 32, ) Trong mục "Sắp xếp nâng cao", ở Chương 15:Việc biến đổi dữ liệu khác, bạn sẽ học cách sắp xếp theo số, hoặc theo thứ tự giảm dần, hay theo kí tự thứ ba của từng xâu, hay bất kì phương pháp nào khác mà bạn chọn
3.4.7 Toán tử chop() và chomp()
Toán tử chop() và chomp() làm việc trên biến mảng cũng như biến vô hướng Mỗi phần
tử của mảng đều có kí tự cuối bị bỏ đi Điều này có thể là thuận tiện khi bạn đọc một danh sách các dòng như các phần tử mảng tách riêng, và bạn muốn bỏ đi dấu dòng mới trong tất cả các dòng ngay lập tức Chẳng hạn:
@stuff = ("hello\n", "world\n", "happy day");
chop(@stuff); # @stuff bây giờ là ("hello", "world", "happy da”)
chomp(@stuff); # @stuff bây giờ là ("hello", "world", "happy day”)
3.5 Hoàn cảnh vô hướng và mảng
Như bạn có thể thấy, từng toán tử đều được thết kế để hoạt động trên một số tổ hợp xác định các vô hướng hay mảng, và cho lại một vô hướng hay mảng Nếu một toán tử trông đợi một toán hạng là vô hướng thì ta nói rằng toán hạng đó là được tính trong hoàn cảnh vô hướng Tương tự, nếu một toán hạng đang trông đợi một giá trị mảng thì
ta nói rằng toán hạng đó là được tính trong hoàn cảnh mảng
Điều này là khá thông thường, nhưng đôi khi bạn nhận được một thao tác hoàn toàn khác tuỳ theo liệu bạn đang trong hoàn cảnh vô hướng hay mảng Chẳng hạn, @jerry cho lại nội dung của mảng @jerry trong hoàn cảnh mảng, nhưng cho lại chiều dài của mảng đó trong hoàn cảnh vô hướng Nhưng sự "huyền ảo" này sẽ được chi chú rõ đối với mỗi toán tử và hàm cụ thể
Nếu bạn muốn buộc một biểu thức phải được tính trong hoàn cảnh vô hướng thì bạn có thể ghép nối một xâu rỗng vào cho nó Chẳng hạn:
@a = ('x','y','z');