Perl và các khái niệm cơ bản
Trang 1Lêi giíi thiÖu
Cuèn s¸ch nµy nãi vÒ g×
Trang 2Biểu diễn hằng kí hiệu cho mảng kết hợpToán tử mảng kết hợp
Bài tập
6: Vào/ra cơ sở
Đa vào từ STDIN
Đa vào từ toán tử hình thoi
Đa ra STDOUT
Bài tập
7: Biểu thức chính qui
Khái niệm về biểu thức chính qui
Cách dùng đơn giản về biểu thức chính quiMẫu
Nói thêm về toán tử đối sánh
Bộ sửa đổi biểu thức
&&, || và ?: xem nh các cấu trúc điều khiểnBài tập
10: Giải quyết tệp và kiểm thử tệp
Trang 3Giải quyết danh mục
Mở và đóng bộ giải quyết danh mục
Đọc bộ giải quyết danh mục
Sửa đổi quyền dùng
Sửa đổi thời hạn
Trang 4Lấy mật hiệu và thông tin nhóm
18: Chuyển các ngôn ngữ khác sang Perl
Chuyển Chơng trình awk sang Perl
Chuyển Chơng trình sed sang Perl
Nhiều, nhiều hàm nữa
Nhiều, nhiều biến đặt sẵn nữa
Xâu ở đây
return (từ chơng trình con)
Toán tử eval (và s///e)
Thao tác bảng kí hiệu với *FRED
Trang 5Ông ấy đã tạo ra Perl khi cố gắng sản xuất ra một số báo cáo từ một cấp bậc các tệp kiểu nh th ngời dùng mạng Usenet về hệ thống báo lỗi, và lệnh awk làm xì hết hơi Larry, một ngời lập trình lời biếng, quyết định thanh toán vấn đề này bằng một công cụ vạn năng mà anh có thể dùng ít nhất cũng ở một nơi khác Kết quả là bản đầu tiên của Perl.
Say khi chơi với bản đầu này của Perl một chút, thêm chất liệu đây đó, Larry
đa nó cho cộng đồng độc giả Usenet, thờng vẫn đợc gọi là “the Net” Ngời dùng thuộc toán phù du nghèo khó về hệ thống trên toàn thế giới (quãng độ chục nghìn ngời) đa lại cho anh phản hồi, hỏi cách làm thế này thế kia, việc này việc khác, nhiều điểm mà Larry cha bao giờ mờng tợng ra về việc giải quyết cho Perl nhỏ bé của mình cả
Nhng kết quả là Perl trởng thành, trởng thành và trởng thành thêm nữa, và cũng cùng tỉ lệ nh lõi của UNIX (với bạn là ngời mới, toàn bộ lõi UNIX đợc dùng chỉ khít vào trong 32K! Và bây giờ chúng ta may mắn nếu ta có thể có đợc nó dới một vài mega.) Nó đã trởng thành trong các tính năng Nó đã trởng thành trong tính khả chuyển Điều mà có thời là một ngôn ngữ tí tẹo bây giờ đã có tài liệu sử dụng 80 trang, một cuốn sách của Nutshell 400 trang, một nhóm tin Usenet với 40 nghìn thuê bao, và bây giờ là đoạn giới thiệu nhẹ nhàng này
Larry vẫn là ngời bảo trì duy nhất, làm việc trên Perl ngoài giờ khi kết thúc công việc thờng ngày của mình Và Perl thì vẫn phát triển
Một cách đại thể thì lúc mà cuốn sách này đạt tới điểm dừng của nó, Larry sẽ
đa ra bản Perl mới nhất, bản 5.0, hứa hẹn 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 (Larry bảo tôi rằng không còn mấy dòng lệnh từ lần đa ra trớc, và số ấy cứ ngày càng ít đi mỗi ngày.) Tuy nhiên, cuốn sách này đã đợc thử với Perl bản 4.0 (lần đa ra gần đây nhất khi tôi viết điều này) 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
Trang 6thay đổi lạ cần cho sự tiến bộ.
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 nặng nề hay quá nhậy cảm với tính khả chuyển đối với trình
vỏ, và cũng quá kì lạ hay ngắn ngủi hay phức tạp để lập trình trong C hay một ngôn ngữ công cụ UNIX nào khác
Một khi bạn trở nên quen thuộc với Perl, bạn có thể thấy mình mất ít thời gian
để lấy đợc trích dẫn trình vỏ (hay khai báo C) đúng, và nhiều thời gian hơn để đọc tin trên Usenet và đi trợt tuyết trên đồi; vì Perl là một công cụ lớn tựa nh chiếc đòn bẩy Các cấu trúc chặt chẽ của Perl cho phép bạn tạo ra (với tối thiểu sự om sòm nhặng sị) một số giải pháp có u thế rất trần lặng hay những công cụ tổng quát Cũng vậy, bạn có thể lôi những công cụ này sang công việc tiếp, vì Perl là khả chuyển cao độ và lại có sẵn, cho nên bạn sẽ có nhiều thời gian hơn để đọc tin Usenet và trợt tuyết
Giống nh mọi ngôn ngữ, Perl có thể “chỉ viết” - tức là có thể viết ra chơng trình mà không thể nào đọc đợc Nhng với sự chú ý đúng đắn, bạn có thể tránh đợc
sự kết tội thông thờng này Quả thế, đôi khi Perl trông nh nổi tiếng với những cái không quen thuộc, nhng với ngời lập trình đã thạo Perl, nó tựa nh những dòng có tổng kiểm tra với một sứ mệnh trong cuộc đời Nếu bạn tuân theo những hớng dẫn của cuốn sách này thì chơng trình của bạn sẽ dễ đọc và dễ bảo trì, và chúng có lẽ
sẽ không thắng trong bất kì cuộc tranh luận Perl khó hiểu nào
Tính sẵn có
Nếu bạn nhận đợc
Perl: not found
khi bạn thử gọi Perl từ lớp vỏ thì ngời quản trị hệ thống của bạn cũng chẳng lên cơn sốt Nhng thậm chí nếu nó không có trên hệ thống của bạn, thì bạn vẫn có thể lấy đợc nó không mất tiền (theo nghĩa “ăn tra không mất tiền”)
Perl đợc phân phối theo phép công cộng GNU, nghĩa là thế này, “bạn có thể phân phát chơng trình nhị phân Perl chỉ nếu bạn làm ra chơng trình gốc có sẵn cho mọi ngời dùng không phải trả tiền gì cả, và nếu bạn sửa đổi Perl, bạn phải phân phát chơng trình gốc của bạn cho nơi sửa đổi của bạn nữa.” Và đó là bản chất của cho không Bạn có thể lấy chơng trình gốc của Perl với giá của một băng trắng hay vài mêga byte qua đờng dây Và không ai có thể khoá Perl và bán cho bạn chỉ mã nhị phân với ý tởng đặc biệt về “cấu hình phần cứng đợc hỗ trợ”
Trong thực tế, nó không chỉ là cho không, nhng 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 Đấy là vì
bộ trình này tới với bản viết cấu hình bí quyết đợc gọi là Cấu hình, cái sẽ móc và chọc vào các danh mục hệ thống để tìm những thứ 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, 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, ngời đã bị nghiện Perl đem nó
Trang 7sang Amiga, Atari ST, họ Macintosh, VMS, OS/2, thậm chí MS/DOS - và có lẽ còn nhiều hơn nữa vào lúc bạn đọc cuốn sá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 bạn phải hỏi quanh (trên nhóm tin Usenet chẳng hạn) để có đợc thông tin mới nhất Nếu bạn hoàn toàn không biết gì, 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).
Hỗ trợ
Perl là con đẻ của Larry Wall, và vẫn đang đợc anh ấy nâng niu Báo cáo lỗi và yêu cầu nâng cao nói chung đều đợc sửa chữa trong các lần đa ra sau, nhng anh ấy cũng chẳng có nghĩa vụ nào để làm bất kì cái gì với chúng cả Tuy thế Larry thực
sự thích thú nghe từ tất cả chúng ta, và cũng làm việc thực sự để thấy Perl đợc dùng trên qui mô thế giới E-mail trực tiếp cho anh ấy nói chung đều nhận đợc trả lời (cho dù đấy chỉ đơn thuần là máy trả lời email của anh ấy), và đôi khi là sự đáp ứng con ngời
ích lợi hơn việc viết th trực tiếp cho Larry là 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 Nếu bạn có thể gửi email trên Internet, nhng cha vào Usenet, thì bạn có thể tham gia nhóm này bằng cách gửi một yêu cầu tới perl-users-request@virgina.edu, yêu cầu sẽ tới một ngời
có thể nối bạn với cửa khẩu email hai chiều trong nhóm, và cho bạn những hứong dẫn về cách làm việc
Khi bạn tham gia một nhóm tin, bạn sẽ thấy đại loại có khoảng 30 đến 60 “th” mỗi ngày (vào lúc bản viết này đợc soạn thảo) trên đủ mọi chủ đề từ câu hỏi của ngời mới bắt đầu cho tới vấn đề chuyển chơng trình phức tạp và vấn đề giao diện,
và thậm chí cả một hay hai chơng trình khá lớn
Nhóm tin gần nh đợc những chuyên gia Perl điều phối Phần lớn thời gian, câu hỏi của bạn đều có sự trả lời trong vòng vài phút khi bài tin bạn tới tủ nối Usenet chính Bạn hãy thử mức độ hỗ trợ từ nhà sản xuất phần mềm mình a chuộng về việc cho không này! Bản thân Larry cũng đọc về nhóm khi thời gian cho phép, và
đôi khi đã xen các bài viết có thẩm quyền vào để chấm dứt việc cãi nhau hay làm sáng tỏ một vấn đề Sau rốt, không có Usenet, có lẽ không thể có chỗ để dễ dàng công bố Perl cho cả thế giới
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) Programming Perl đợc xem
nh “Sách con lừa” vì bìa của nó vẽ con vật này (hệt nh cuốn sách này có lẽ sẽ đợc biết tới với tên sách lạc đà không bớu) Sách con lừa chứa thông tin tham khảo đầy
đủ về Perl dới dạng đóng gọn gàng Sách con lừa cũng bao gồm một bảng tra tham khảo bật ra tài tình mà chính là nguồn a chuộng của cá nhân tôi về thông tin Perl
Các khái niệm cơ bản
Một bản viết vỏ không gì khác hơn là một dãy các lệnh vỏ nhồi vào trong một tệp văn bản Tệp này “đợc làm cho chạy” bằng cách bật một bit thực hiện (qua
Trang 8chmod +x filename) và rồi gõ tên của tệp đó vào lời nhắc của vỏ Bingo, một
ch-ơng trình vỏ lớn Chẳng hạn, một bản viết để chạy chỉ lệnh date theo sau bởi chỉ lệnh who có thể đợc tạo ra và thực hiện nh thế này:
$ echo date > somecript
$ echo who > somecript
Tơng tự nh thế, một chơng trình Perl là một bó các câu lệnh và định nghĩa Perl
đợc ném vào trong một tệp Rồi bạn bật bit thực hiện và gõ tên của tệp này tại lời nhắc của vỏ Tuy nhiên, tệp này phải chỉ ra rằng đây là một chơng trình Perl và không phải là chơng trình vỏ, nên chúng ta cần một bớc phụ
#! /usr/bin/perl
làm dòng đầu tiên của tệp này Nhng nếu Perl của bạn bị kẹt vào một nơi không chuẩn, hay hệ điều hành tựa UNIX của bạn không hiểu dòng #!, thì bạn có thêm việc phải làm Hãy hỏi ngời cài đặt Perl về điều này Các thí dụ trong sách này giả sử rằng bạn dùng cơ chế thông thờng này
Perl là một ngôn ngữ phi định dạng kiểu nh C - khoảng trắng giữa các hiệu bài (những phần tử của chơng trình, nh print hay +) là tuỳ chọn, trừ phi hai hiệu bài đi với nhau có thể bị lầm lẫn thành một hiệu bà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, dòng mới, về đầu dòng hay kéo giấy.) 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 đó, nhng chúng sẽ đợc trỏ ra khi ta nói tới chúng Bạn có thể giả thiết rằng loại và khối lợng khoảng trắng giữa các hiệu bài là tuỳ ý trong các trờng hợp khác
Mặc dầu 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, một cách điển hình chơng trình Perl cũng hay đợc viết tụt lề nh chơng trình
C, với những phần câu lệnh lồng nhau đợc viết tụt vào hơn so với phần bao quanh Bạn sẽ thấy đầy những thí dụ chỉ ra phong cách viết tụt lề điển hình trong toàn bộ cuốn sách này
Cũng giống nh bản viết về vỏ, 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ề trình “chính” main nh trong C
Chú thích của Perl giống nh chú thích của lớp vỏ (hiện đại) Bất kì cái gì nằm giữa một dấu thăng (#) tới cuối dòng đều là một chú thích Không có khái niệm về chú thích trên nhiều dòng nh C
Không giống hầu hết các lớp vỏ (nhng 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
Trang 9có 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 biến 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 đơn thuần dựa trên nền tảng là C đợc biên dịch
Việc biên dịch này không mất thời gian - sẽ là phi hiệu quả nếu một chơng trình Perl cực lớn lại chỉ thực hiện một nhiệm vụ nhỏ bé chóng vánh (trong số nhiều nhiệm vụ tiềm năng) và rồi ra, vì thời gian chạy cho chơng trình sẽ nhỏ xíu nếu so với thời gian 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 ngồi đâu đó trút đầy không gian đĩa Theo một cách nào đó, nó là tốt nhất cho cả hai loại này Phải thú thực, việc ẩn đi mã đích đã dịch giữa những lời gọi thì hay, và đó là trong danh sách mong ớc cho Perl tơng lai của Larry
Dạo qua Perl
Chúng ta bắt đầu cuộc hành trình của mình qua Perl bằng việc đi dạo một chút Việc đi dạo nà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 là cực kì ngắn gọn - mỗi vùng chủ
đề đều đợc thảo luận chi tiết hơn rất nhiều về sau trong cuốn sách này Nhng cuộc
đi dạo nhỏ này sẽ cho bạn kinh nghiệm nhanh chóng về ngôn ngữ, và bạn có thể quyết định liệu bạn có thực sự muốn kết thúc cuốn sách này hay đọc thêm các tin Usenet hay chạy đi chơi trợt tuyết
Chơng trình “Xin chào mọi ngời”
Ta hãy nhìn vào một chơng trình nhỏ mà thực tế có làm điều gì đó Đây là
ch-ơng trình “Xin chào mọi ngời”:
#!/usr/bin/perl
print “Xin chào mọi ngời\n”;
Dòng đầu tiên là câu thần chú nói rằng đây là chơng trình Perl Nó cũng là lời chú thích cho Perl - hãy nhớ rằng lời chú thích là bất kì cái gì nằm sau dấu thăng cho tới cuối dòng, giống nh hầu hết các lớp vỏ hiện đại hay awk
Dòng thứ hai là toàn bộ phần thực hiện đợc của chơng trình này Tại đây chúng ta thấy câu lệnh print Từ khoá print bắt đầu chơng trình, và nó có một đối, một xâu văn bản kiểu C Bên trong xâu này, tổ hợp kí tự \n biểu thị cho kí tự dòng mới; hệt nh trong C Câu lệnh print đợc kết thúc bởi dấu chấm phẩy (;) Giống nh
C, tất cả các câu lệnh đơn giản đều kết thúc bằng chấm phẩy*
Khi bạn gọi chơng trình này, phần lõi sẽ gọi bộ thông dịch Perl, phân tích câu toàn bộ chơng trình (hai dòng, kể cả dòng chú thích đầu tiên) và rồi thực hiện dạng đã dịch Thao tác đầu tiên và duy nhất là thực hiện toán tử print, điều này gửi
* Dấu chấm phẩy có thể bỏ đi khi câu lệnh này là câu lệnh cuối của một khối hay tệp hay eval.
Trang 10đối của nó ra lối ra Sau khi chơng trình đã hoàn tất, thì tiến trình Perl ra, cho lại một mã ra thành công cho lớp vỏ.
Hỏi câu hỏi và nhớ kết quả
Ta hãy thêm một chút phức tạp hơn Từ Xin chào mọi ngời là một sự đụng chạm lạnh nhạt và cứng rắn Ta hãy làm cho chơng trình gọi bạn theo tên bạn Để làm việc này, ta cần một chỗ giữ tên, một cách hỏi tên, và một cách nhận câu trả lời
Một loại đặt chỗ giữ giá trị (tựa nh một tên) là 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 bạn Chúng ta sẽ đi chi tiết hơn trong Chơng 2, Dữ liệu vô hớng, về những gì mà biến này có thể giữ, và những gì bạn có thể làm với chúng Hiện tại, giả sử rằng bạn có thể giữ một số hay 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 cái vào Chơng trình trớc đã chỉ ra cho ta cách nhắc - dùng toán tử print
Và cách để nhận một dòng từ thiết bị cuối là với toán tử <STDIN>, mà (nh ta sẽ dùng nó ở đây) lấy một dòng cái vào Ta gán cái vào này cho biến $name Điều này cho ta chơng trình:
print “Tên bạn là gì? : ”;
$name = <STDIN> ;
Giá trị của $name tại điểm này có một dấu dòng mới kết thúc (Randal có trong Randal\n) Để vứt bỏ điều đó, chúng ta dùng toán tử chop(), toán tử lấy một biến vô hớng làm đối duy nhất và bỏ đi kí tự cuối từ giá trị xâu của biến:
chop($name);
Bây giờ tất cả những gì ta cần làm là nói Xin chào, tiếp đó là giá trị của biến
$name, mà ta có thể thực hiện theo kiểu vỏ bằng cách nhúng biến này vào bên trong xâu có ngoặc kép:
print “Xin chào, $name!\n”;
Giống nh lớp vỏ, nếu ta muốn một dấu đô la thay vì tham khảo biến vô hớng, thì ta có thể đặt trớc dấu đô la với một dấu sổ chéo ngợc
Trang 11Câu lệnh if chọn xem khối câu lệnh nào (giữa các dấu ngoặc nhọn sánh đúng)
là đợc thực hiện - nếu biểu thức là đúng, đó là khối thứ nhất, nếu không thì đó là khối thứ hai
Đoán từ bí mật
Nào, vì chúng ta đã có một tên nên ta hãy để cho một ngời chạy chơng trình
đoán một từ bí mật Với mọi ngời ngoại trừ Randal, chúng ta sẽ để cho chơng trình
cứ hỏi lặp lại để đoán cho đến khi nào ngời này đoán đợc đúng Trớc hết ta hãy xem chơng trình này và rồi xem giải thích:
while ($guess ne $secrectword) {
print “Sai rồi, thử lại đi Từ bí mật là gì?”;
$guess = <STDIN>;
chop($guess);
}
}
Trớc hết, ta định nghĩa từ bí mật 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 Randal) sẽ đợc yêu cầu (với
Trang 12một câu lệnh print khác) đoán chữ Lời đoán rồi đợc đem ra so sánh với từ bí mật bằng việc dùng toán tử ne, mà sẽ cho lại đúng nếu các xâu này không bằng nhau (đây là toán tử logic ngợc với toán tử eq) Kết quả của việc so sánh sẽ kiểm soát cho trình while, chu trình này thực hiện khối thân cho tới khi việc so sánh vẫn còn
đúng
Tất nhiên, chơng trình này không phải là an toàn lắm, vì bất kì ai mệt với việc
đoán cũng đều có thể đơn thuần ngắt chơng trình và quay trở về lời nhắc, hay thậm chí còn nhìn qua chơng trình gốc để xác định ra từ Nhng, chúng ta hiện tại cha
định viết một hệ thống an toàn, chỉ xem nh một thí dụ cho trang hiện tại của cuốn sách này
Nhiều từ bí mật
Ta 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 máy tính trên cơ sở ngày làm việc thờng lệ
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 thâm nhập độc lập Toàn bộ mảng cũng có thể đợc trao cho một giá trị trong một cú đột nhập 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 tốt có thể có:
@words = (“camel”, “llama”, “oyster”);
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ể thâm nhập vào từng phần tử bằng việc dùng một tham khảo chỉ số Cho nên $words[0] là camel, $words[1] là llama,
$words[2] là oyster Chỉ cố 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à oyster (Tham khảo chỉ số bắt đầu với $ thay vì @ vì 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:
Trang 13$i = 0; # thử từ này trớc hết
$correct = “ có thể”; # từ đoán có đúng hay không?
while ($correct eq $guess) { # cứ kiểm tra đến khi biết
if ($words[$i] eq $guess) { # đúng không
$correct = “có”; # có
} elsif ($i < 2) { # cần phải xét thêm từ nữa?
$i = $i + 1; # nhìn vào từ tiếp lần sau
} # kết thúc của while not correct
} # kết thúc của not Randal“ ”
Bạn sẽ để ý 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, hay rằng chúng ta không tìm thấy
Chơng trình này cũng chỉ ra 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 khối else cùng với một điều kiện if mới, nhng không lồng bên trong cặp dấu ngoặc nhọn 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 một dây chuyền phân tầng if-elsif-elsif-elsif-else
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ì ta cần một bảng sánh giữa ngời và từ:
Chú ý rằng cả Betty và Wilma đều có cùng từ bí mật Điều này là đợc
Cách dễ nhất để cất giữ một bảng nh thế trong Perl là bằng một mảng kết hợp Mỗi phần tử của mảng này giữ một giá trị vô hớng tách biệt (hệt nh kiểu mảng khác), nhng các mảng lại đợc tham khảo tới theo khoá, mà có thể là bất kì giá trị vô hớng nào (bất kì xâu hay số, kể cả số không nguyên và giá trị âm) Để tạo ra một mảng kết hợp đợc gọi là %words (chú ý % chứ không phải là @) với khoá và
Trang 14giá trị đợc cho trong bảng trên, ta gán một giá trị cho %words (nh ta đã làm nhiều trớc đây với mảng khác):
%words = (“fred”, “camel”, “barney”, “llama”,
“betty”, “oyster”, “wilma”, “oyster”) ;Mỗi cặp giá trị trong danh sách đều biểu thị cho một khoá và giá trị tơng ứng của nó trong mảng kết hợp Chú ý rằng ta đã bẻ phép gán này ra hai dòng mà không có bất kì loại kí tự nối dòng nào, vì khoảng trắng nói chung là vô nghĩa trong chơng trình Perl
Để tìm ra từ bí mật cho Betty, ta cần dùng Betty nh khoá trong một tham khảo vào mảng kết hợp %words, qua một biểu thức nào đó nh %words{“betty”} Giá trị của tham khảo này là oyster, tơng tự nh điều ta đã làm trớc đây với mảng khác Cũng
nh trớc đây, khoá có thể là bất kì biểu thức nào, cho nên đặt $person với betty và tính $words{$person} cũng cho oyster
Gắn tất cả những điều này lại ta đợc chơng trình nh thế này:
#! /usr/bin/perl
%words = (“fred”, “camel”, “barney”, “llama”,
“betty”, “oyster”, “wilma”, “oyster”) ;print “Tên bạn là gì?” ;
print “Xin chào, $name!\n”; # chào thông thờng
$secretword = $words{$name}; # lấy từ bí mật
print “Từ bí mật là gì?” ;
$guess = <STDIN>;
chop($guess);
while ($correct ne $secretwords) {
print “Sai rồi, thử lại đi Từ bí mật là gì? ;”
$guess = <STDIN>;
chop($guess);
} # kết thúc của while
} # kết thúc của not Randal“ ”
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ó:
[ phần còn lại của chơng trình đã bị xoá ]
$secretword = $words{$name}; # lấy từ bí mật
if ($secretword eq “”) { # ấy, không thấy
* Đợc, đấy chính là giá trị undef, nhng nó trông nh một xâu rỗng cho toán tử eq
Trang 15$secretword = “đồ cáu kỉnh”; # chắc chắn, sao không là vịt?
}
print “Từ bí mật là gì?” ;
[ phần còn lại của chơng trình đã bị xoá ]
Giải quyết định dạng cái vào thay đổi
Nếu tôi đa vào Randal L Schwartz hay randal thay vì Randal thì tôi sẽ bị đóng cục lại với phần ngời dùng còn lại, vì việc so sánh eq thì lại so sánh đúng sự bằng nhau 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 Randal, thay vì chỉ là một xâu bằng Randal 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 hay grep, biểu thức chính qui trong Perl để sánh bất kì xâu nào bắt
đầu với Randal là ^Randal Để sánh xâu này với xâu trong $name, chúng ta dùng toán tử sánh nh sau:
Điều này gần nh thế, nhng nó lại không giải quyết việc lựa ra randal hay loại
bỏ Randall Để chấp nhận randal, chúng ta thêm tuỳ chọn bỏ qua hoa thờng, một chữ i nhỏ đợc thêm vào sau dấu sổ chéo đóng Để loại bỏ Randall, 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 của \b trong biểu thức chính qui Điều này đảm bảo rằng kí tự đi sau l đầu tiên trong biểu thức chính qui không phải là một kí tự khác Điều này làm thay đổi biểu thức chính qui thành /^randal\b/i, mà có nghĩa là “randal 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.”
Khi gắn tất cả lại với phần còn lại của chơng trình thì nó sẽ giống nh thế này:
#! /usr/bin/perl
%words = (“fred”, “camel”, “barney”, “llama”,
“betty”, “oyster”, “wilma”, “oyster”) ;print “Tên bạn là gì?” ;
Trang 16$secretword = $words{$name}; # lấy từ bí mật
if ($secretword eq “”) { # ấy, không thấy
$secretword = “đồ cáu kỉnh”; # chắc chắn, sao không là vịt?
}
print “Từ bí mật là gì?” ;
$guess = <STDIN>;
chop($guess);
while ($correct ne $secretwords) {
print “Sai rồi, thử lại đi Từ bí mật là gì?”;
$guess = <STDIN>;
chop($guess);
} # kết thúc của while
} # kết thúc của not Randal“ ”
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 Xin chào, mọi ngời, nhng nó vẫn còn rất nhỏ bé và làm việc đợc, và nó quả làm đợc tí chút với cái ngắn xíu vậy đây chính là cách thức của Perl
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) Không chỉ có thế, nhng cách thức Perl giải quyết cho việc đối sánh xâu là cách nhanh nhất trên hành tin này, cho nên bạn không bị mất hiệu năng (Một chơng trình giống nh grep đợc
viết trong Perl thờng đánh bạ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 cái vào Điều này có nghĩa là thậm chí grep không thực hiện một việc của nó thật tốt.)
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 Randal hay randal hay Randal L Schwartz, nhng với những ngời khác thì sao? Barney vẫn phải nói đúng barney (thậm chí không đ-
ợc có barney với một dấu cách theo sau)
Để công bằng cho Barey, chúng ta cần nắm đợc từ đầu của bất kì cái gì đợc đa vào, và rồi 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ử substitute, 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ử translate, để đặt xâu 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 thấp) 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 đó, nhng 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à
Trang 17xâ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:
nh thế*
Gắn tất cả lại với mọi thứ khác sẽ cho kết quả trong:
#! /usr/bin/perl
%words = (“fred”, “camel”, “barney”, “llama”,
“betty”, “oyster”, “wilma”, “oyster”) ;print “Tên bạn là gì?” ;
$name = <STDIN> ;
chop($name);
$original_name = $name; # cất giữ để chào mừng
$name =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$name =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
if ($name eq “randal” ) {
print “Xin chào, Randal! May quá anh ở đây!\n”;
} else {
print “Xin chào, $original_name!\n”; # chào thông thờng
$secretword = $words{$name}; # lấy từ bí mật
if ($secretword eq “”) { # ấy, không thấy
$secretword = “đồ cáu kỉnh”; # chắc chắn, sao không là vịt?
}
print “Từ bí mật là gì?” ;
$guess = <STDIN>;
chop($guess);
while ($correct ne $secretwords) {
print “Sai rồi, thử lại đi Từ bí mật là gì? ;”
$guess = <STDIN>;
chop($guess);
} # kết thúc của while
} # kết thúc của not Randal“ ”
* Các chuyên gia sẽ lu ý rằng tôi cũng đã xây dựng một cái gì đó tựa nh s/(\S*).*/\L$1/ để làm tất cả điều này trong một cú đột kích, nhng các chuyên gia có lẽ sẽ không đọc mục này.
Trang 18Bạn hãy để ý đến cách thức biểu thức chính qui sánh đúng với tên tôi Randal
đã lại trở thành việc so sánh đơn giản Sau rốt, cả Randal L Schwartz và Randal đều trở thành randal sau khi việc thay thế và dịch Và mọi ngời khác cũng có đợc sự công bằng, vì Fred và Fred Flinstone cả hai đều trở thành fred, Barney Rubble và Barney, the little guy sẽ trở thành barney, vân vân
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 nét là một trong nhiều điểm mạnh của Perl
Tuy nhiên, chém 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 chém vào tên, cần phải cất giữ nó vào trong @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 thấp và có thể có chiều dài gần nh không giới hạn.) Vậy ta
có thể làm tham khảo tới $original_name về sau
Perl có nhiều cách để giám sát và chặt 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
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 bạ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 (hay đã 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ị cho lại Một chơng trình con đợc xác định một khi nào đó trong chơng trình, và rồi có thể đợc dùng lặp đi lặp lại bằng việc gọi từ bên trong bất kì biểu thức nào
Với chơng trình nhỏ nhng tăng trởng nhanh của chúng ta, ta hãy tạo ra một
ch-ơng trình con tên là &good_word (tất cả các tên chch-ơng trình con đều bắt đầu với một dấu và &) mà nhận một tên đã sạch và một từ đoán, rồi cho lại true nếu từ đó
là đúng, và cho lại false nếu không đúng Việc xác định chơng trình con đó tựa
nh thế này:
sub good_word {
local($somename, $someguess) = @_; # tên tham biến
$somename =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$somename =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
if ($somename eq “randal”) { # không nên đoán
1; #giá trị cho lại là true
} elsif (($words{$somename} || “đồ cáu kỉnh”) eq $someguess) {
1; # giá trị cho lại là true
} else {
0; # cho lại giá trị false
}
Trang 19Trớ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 (không có dấu và &) tiếp nữa là một khối mã lệnh (đợc định biên bởi dấu ngoặc nhọn) Định nghĩa này có thể để vào bất kì
đâu trong tệp chơng trình, nhng 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àm việc sao các giá trị của hai tham biến của chơng trình này vào hai biến cục bộ có tên
$somename và $someguess (local() 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 Randal không nên làm nó thành chơng trình con này, nhng ngay cả nếu nó có, thì dù từ nào đợc đoán cũng đều OK cả
Biểu thức cuối cùng đợc tính trong chơng trình con là cho lại giá trị Chúng ta
sẽ thấy cách cho lại giá trị đợc dùng sau khi tôi 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} || “đồ cáu kỉnh”) eq $someguess
Vật thứ nhất bên trong dấu ngoặc là mảng kết hợp quen thuộc của ta, cho 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 đồ cáu kỉnh là toán tử || (phép hoặc logic) nh đợc dùng trong C và awk và các
vỏ khác Nếu việc tra cứu từ mảng kết hợp có một giá trị (nghĩa là khoá
$somename là trong mảng), thì giá trị của biểu thức là giá trị đó Nếu khoá không tìm đợc, thì xâu đồ cáu kỉnh sẽ đợc dùng thay Đây chính là một vật 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 đồ cáu kỉnh, 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ì ta cho lại 1, nếu không ta cho lại 0
Bây giờ ta hãy tích hợp tất cả những điều này với phần còn lại của chơng trình:
#! /usr/bin/perl
%words = (“fred”, “camel”, “barney”, “llama”,
“betty”, “oyster”, “wilma”, “oyster”) ;print “Tên bạn là gì?” ;
$name = <STDIN> ;
chop($name);
if ($name =~ /^randal\b/i ) { # trở lại cách khác
print “Xin chào, Randal! May quá anh ở đây!\n”;
} else {
print “Xin chào, $original_name!\n”; # chào thông thờng
Trang 20print “Từ bí mật là gì?” ;
$guess = <STDIN>;
chop($guess);
while ( ! &good_word($name, $guess)) {
print “Sai rồi, thử lại đi Từ bí mật là gì?”;
$guess = <STDIN>;
chop($guess);
}
}
[ thêm vào định nghĩa của &good_word ở đây ]
Chú ý rằng chúng ta đã quay trở lại với biểu thức chính qui để kiểm tra Randal, 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 đối, $name và $guess Bên trong chơng trình con này, giá trị của $somename đợc đặt từ tham biến 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 cho lại (hoặc 1 hoặc 0, nhớ lại định nghĩa đã nêu
tr-ớc đây) là âm với toán tử tiền tố ! (phép phủ định logic) Nh trong C, toán tử này cho lại đúng nếu biểu thức đi sau là sai, 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 Bạn có thể đọc điều này là “trong khi không phải
là từ đúng ” Nhiều chơng trình Perl viết tốt đọc rất giống tiếng Anh, đa lại cho bạn một chút tự do với Perl hay tiếng Anh (Nhng bạn chắc chắn không đoạt giải Pulitzer theo cách đó.)
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 Điều này không đặc biệt là hay, nhng chẳng có gì so sánh đợc với static của C đối với chơng trình con - nói chung, tất cả các biến nếu không nói khác đi mà đợc tạo ra với toán tử local() thì đều là toàn cục đối với toàn bộ chơng trình Trong thực tế, đây chỉ là vấn đề nhỏ, và ngời ta thì có đợc tính sáng tạo về cách đặt tên biến dài hạn
Chuyển danh sách từ bí mật vào tệp riêng biệt
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 Betty quyết định rằng từ bí mật của cô sẽ là swine thay vì oyster Điều này có thể thành phiền phức, đặc biệt khi xem xét tới việc Betty lại thờng xuyên thích thay đổi ý định
Cho nên, ta hãy đặt danh sách 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
Trang 21trì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 đó:
sub init_words {
open (WORDSLIST, “wordslist”);
while ($name = <WORDSLIST>) {
Đị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:
Toán tử open() tạo ra một tớc hiệu tệp có tên WORDSLIST bằng cách liên kết
nó với một tệp mang tên wordslist trong danh mục hiện tại Lu ý rằng tớc hiệu tệp không có kí tự là lạ phía trớc nó nh ba kiểu biến vẫn có Cũng vậy, tớc hiệu tệp nói chung là chữ hoa - mặc dầu chúng không nhất thiết phải là nh thế - bởi những lí do
sẽ nêu chi tiết về sau
Chu trình while đọc các dòng từ tệp wordslist (qua tớc hiệu tệp WORDSLIST) mỗi lần một dòng Mỗi dòng đều đợc cất giữ trong biến $name Khi đạt đến cuối tệp thì giá trị cho lại bởi toán tử <WORDSLIST> là xâu rỗng* , mà sẽ sai cho cho trình while, và kết thúc nó Đó là cách chúng ta đi ra ở cuối
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 dòng mới bằng việc dùng toán
tử chop() Rồi, ta phải đọc dòng tiếp để lấy từ bí mật, giữ nó trong biến $word Nó
* Về mặt kĩ thuật thì đấy lại là undef, nhng cũng đủ gần cho thảo luận này
Trang 22nữa cũng phải bỏ dấu 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, cho nên phần còn lại của chơng trình có thể thâm 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()
(T-ớc hiệu tệp dẫu sao cũng đợc tự động đóng lại khi chơng trình thoát ra, nhng tôi
đang định làm cho gọn.)
Đị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 bọc tất cả những điều này có thể giống thế này:
if ($name =~ /^randal\b/i ) { # trở lại cách khác
print “Xin chào, Randal! May quá anh ở đây!\n”;
while ( ! &good_word($name, $guess)) {
print “Sai rồi, thử lại đi Từ bí mật là gì?”;
open (WORDSLIST, “wordslist”);
while ($name = <WORDSLIST>) {
local($somename, $someguess) = @_; # tên tham biến
$somename =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$somename =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
Trang 23if ($somename eq “randal”) { # không nên đoán
1; #giá trị cho lại là true
} elsif (($words{$somename} || “đồ cáu kỉnh”) eq $someguess) {
1; # giá trị cho lại là true
} else {
0; # cho lại giá trị false
}
}
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 biến nào
đợc truyền cả, cho nên chúng ta đợc tự do bỏ đi dấu ngoặc tròn Cũng vậy, giá trị cho lại không đợc dùng trong tính toán thêm, thì cũng là tốt vì ta đã không cho lại
điều gì đáng để ý (Giá trị của close() thông thờng là đúng.)
Toán tử open() cũng đợc dùng để mở các tệp đa ra, hay mở chơng trình nh tệp (đã đợc biểu diễn ngắn gọn) Tuy thế, việc vét hết về open() sẽ đợc nêu về sau trong cuốn sách này, trong Chơng 10, Tớc hiệu tệp và kiểm tra tệp
Đảm bảo một lợng an toàn giản dị
“Danh sách các từ bí mật phải thay đổi ít nhất một lần mỗi tuần!” ông Trởng ban danh sách từ bí mật kêu lên Thôi đợc, chúng ta không thể buộc danh sách này khác đi, nhng 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 cha đợc thay đổi trong hơn một tuần
Cách tốt nhất để làm điều này là trong chơng trình con &init_words - chúng ta
đã nhìn vào tệp ở đó Toán tử Perl -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 trt-ớc, cho nên ta chỉ cần xem liệu giá trị này có lớn hơn bẩy hay không đối với tớc hiệu tệp WORDSLIST:
sub init_words {
open (WORDSLIST, “wordslist”);
if (-M WORDSLIST > 7) { # tuân thủ theo đờng lối quan liêu
die “Rất tiếc, danh sách từ cũ hơn bẩy ngày rồi.”;
Trang 24một thông báo trên thiết bị cuối* , và bỏ chơng trình trong một cú bổ nhào rơi xuống.
Phần còn lại của chơng trình vẫn không đổi, cho nên trong mối quan tâm tới việc tiết kiệm cây cối, tôi sẽ không lặp lại nó ở đây
Bên cạnh việc lấy tuổi 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
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 (nhờ có tính mô đun) vì ta có tất cả thông tin ngay đây
Th sẽ đợc gửi cho bạn nếu bạn gõ địa chỉ th của riêng mình vào chỗ mà chơng trình nói “Địa chỉ bạn ở đây.” Đâ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 {
local($somename, $someguess) = @_; # tên tham biến
$somename =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$somename =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
if ($somename eq “randal”) { # không nên đoán
1; #giá trị cho lại là true
} elsif (($words{$somename} || “đồ cáu kỉnh”) eq $someguess) {
1; # giá trị cho lại là true
} else {
open(MAIL, “|mail Địa_chỉ_bạn_ở_đây”);
print MAIL “tin xấu: $somename đã đoán $someguess\n”;
0; # cho lại giá trị false
}
}
Câu lệnh mới thứ nhất ở đây là open(), mà có một kí hiệu đờng ống (|) trong tên tệp Đây là một chỉ dẫn đặc biệt rằng ta đang mở một chỉ lệnh thay vì một tệp Vì đờng ống là tại chỗ bắt đầu của chỉ lệnh nên ta đang mở một chỉ lệnh để ta có thể ghi lên nó (nếu bạn đặt đờng ống tại cuối thay vì đầu thì bạn có thể đọc cái ra của chỉ lệnh.)
Câu lệnh tiếp, print, chỉ ra rằng một tớc hiệu tệp giữa từ khoá print và giá trị đợc
in ra chọn tớc hiệu tệp đó làm cái ra, thay vì STDOUT* Điều này có nghĩa là thông báo sẽ kết thúc nh cái vào cho chỉ lệnh mail
Cuối cùng, ta đóng tớc hiệu tệp, mà sẽ bắt đầu để mail gửi dữ liệu của nó theo
* Thực tại là STDERR, nhng đấy thông thờng là màn hình
* Về mặt kĩ thuật thì tớc hiệu tệp này hiện đợc chọn Điều đó sẽ đợc nói nhiều tới về sau.
Trang 25cách của nó.
Để cho đúng, chúng ta có thể gửi câu trả lời đúng cũng nh câu trả lời sai, nhng rồi ai đó đọc qua vai tôi (hay núp trong hệ thống th) trong khi tôi đang đọc th mà
có thể lấy quá nhiều thông tin có ích
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
đối, 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 Backquotes (giống nh backquotes của vỏ) cho một cách dễ dàng nắm đợc cái ra của một chỉ lệnh nh dữ liệu Tất cả những điều này sẽ đợc mô tả trong Chơng 14, Quản lí tiến trình, cho nên bạn nhớ
đọc
Nhiều tệp từ bí mật trong danh 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ì cái gì trong danh mục hiện tại mà có tận cùng
là secret Với lớp vỏ, ta nói:
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
Cho nên nếu danh mục hiện tại có chứa fred.secret và barney.secret, thì
$filename là barney.secret ở bớc đầu qua chu trình while (tên tới theo trật tự sắp của
* Lại undef lần nữa
Trang 26bảng chữ) ở bớc thứ hai, $filename là fred.secret Và không có tình huống thứ ba vì núm cho lại xâu rỗng khi lần thứ ba đợc gọi tới, làm cho chu trình while thành sai, gây ra việc ra khỏi chơng trình con.
Bên trong chu trình while, chúng ta mở tệp và kiểm chứng rằng nó đủ gần đây (ít hơn bẩy ngày từ lần sửa đổi trớc) Với những tệp đủ gần, chúng ta duyệt qua
5, Mảng kết hợp.)
Nhng chúng ta biết họ là ai!
Đợc rồi, chúng ta đã hỏi tên ngời dùng khi trong thực tế chúng ta có thể lấy tên của ngời dùng hiện tại từ hệ thống, bằng việc dùng một vài dòng nh:
@password = getpwuid($<); # lấy dữ liệu mật hiệu
$name = $password[6]; # lấy trờng GCOS
$name =~ s/,.*//; # vứt đi mọi thứ sau dấu phẩy đầu tiên
Dòng đầu tiên dùng số hiệu ngời dùng ID của UNIX (thờng đợc gọi là UID),
tự động hiện diện trong biến Perl $< Toán tử getpwuid() (đợc đặt tên giống nh trình
th viện chuẩn) lấy số hiệu UID và cho lại thông tin từ tệp mật hiệu (hay có thể một
số cơ sở dữ liệu khác) nh một danh sách Chúng ta đánh dấu thông tin này trong mảng @password
Khoản mục thứ bẩy của mảng @password (chỉ số 6) là trờng GCOS, mà thờng
là một xâu có chứa danh sách các giá trị các nhau bởi dấu phẩy Giá trị đầu tiên của xâu đó thờng là tên đầy đủ của ngời
Một khi hai câu lệnh đầu này đã hoàn tất thì chúng ta có toàn bộ trờng GCOS trong $name Tên đầy đủ chỉ là phần thứ nhất của xâu trớc dấu phẩy đầu tiên, cho nên câu lệnh thứ ba vứt đi mọi thứ sau dấu phẩy thứ nhất
Gắn tất cả những điều đó với phần còn lại của chơng trình (nh đã đợc sửa bởi hai thay đổi chơng trình con khác)
#! /usr/bin/perl
&init_words;
@password = getpwuid($<); # lấy dữ liệu mật hiệu
$name = $password[6]; # lấy trờng GCOS
$name =~ s/,.*//; # vứt đi mọi thứ sau dấu phẩy đầu tiên
if ($name =~ /^randal\b/i ) { # trở lại cách khác
print “Xin chào, Randal! May quá anh ở đây!\n”;
} else {
print “Xin chào, $name!\n”; # chào thông thờng
Trang 27print “Từ bí mật là gì?” ;
$guess = <STDIN>;
chop($guess);
while ( ! &good_word($name, $guess)) {
print “Sai rồi, thử lại đi Từ bí mật là gì? ;”
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
close (WORDSLIST);
}
}
sub good_word {
local($somename, $someguess) = @_; # tên tham biến
$somename =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$somename =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
if ($somename eq “randal”) { # không nên đoán
1; #giá trị cho lại là true
} elsif (($words{$somename} || “đồ cáu kỉnh”) eq $someguess) {
1; # giá trị cho lại là true
} else {
open(MAIL, “|mail Địa_chỉ_bạn_ở_đây”);
print MAIL “tin xấu: $somename đã đoán $someguess\n”;
0; # cho lại giá trị false
Trang 28Liệt kê các từ bí mật
Rồi ông phụ trách danh sách từ bí mật 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 Nếu ta gạt sang bên chơng trình từ bí mật một lúc, thì ta sẽ có thời gian để viết một chơng trình báo cáo cho
ông phụ trách
Trớc hết, ta hãy lấy ra tất cả các từ bí mật, bằng việc ăn cắp một đoạn mã trong chơng trình con &init_words:
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
close (WORDSLIST);
}
Tại điểm có đánh dấu “chất liệu mới sẽ đa vào đây,” ta biết ba điều: tên của tệp (trong $filename), tên một ai đó (trong $name), và rằng từ bí mật của một ngời (trong $Word) Sau đây là chỗ để dùng công cụ sinh báo cá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):
dòng định nghĩa trờng vốn xác định số lợng, chiều dài và kiểu của trờng Với định
dạng này, chúng ta có 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:
#!/usr/bin/perl
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
if (-M WORDSLIST < 7) {
while ($name = <WORDSLIST>) {
Trang 29Dòng đầu tiên trong định dạng này chỉ ra một văn bản hằng nào đó (Page) cùng với việc định nghĩa trờng ba kí tự Dòng sau là dòng giá trị trờng, ở đây với một biểu thức Biểu thức này là biến $%, giữ số trang đợc in ra - một giá trị rất có ích trong định dạng đầu trang.
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 cái ra, 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 cái ra 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 định tâm hay căn lề phải, và hỗ trợ cho miền đoạn
Trang 30đợc rót kín Sẽ có nhiều vấn đề về điều này hơn khi ta đi vào các định dạng trong
Chơng 11, Định dạng
Làm cho danh sách từ cũ đó đáng lu ý hơn
Khi ta đọc qua các tệp *.secret trong danh mục hiện tại, ta có thể tìm thấy các tệp quá cũ Cho tới nay, ta đơn thuần nhẩy qua những tệp này Ta hãy đi thêm một bớc nữa - ta sẽ đổi tên chúng thành *.secret.old để cho ls của danh mục sẽ nhanh chóng cho ta những tệp nào quá cũ, đơn thuần theo tên
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);
rename($filename, “$filename.old”);
}close (WORDSLIST);
}
}
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 bẩy ngày, thì nó sẽ đợc đổi tên bằng toán tử rename() Toán tử này lấy hai tham biến,
đổi tệp có tên trong tham biến thứ nhất thành tên trong tham biến 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
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 kết hợp 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 quãng 700 triệu, chỉ ra số giây) 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 ch 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
Nhng, 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
Trang 31trì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 ra.Toán tử dbmopen() ánh xạ một mảng kết hợp vào một tệp đĩa (thực tế là một cặp tệp đĩa) đợc xem nh một DBM Nó đợc dùng nh thế này:
dbmopen(%last_good, “lastdb”, 066);
$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 củ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 phép
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 lần đầu tiên đợc gặp tới) là 0666 Mốt này có nghĩa là bất kì ai cũng có thể đọc hay ghi lên tệp (Các bit phép tệp đợc mô tả trong manpage chmod(2).)
Câu lệnh thứ hai chỉ ra rằng chúng ta dùng mảng kết hợp đã đợc ánh xạ này hệt nh mảng kết hợp 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 Và, khi mảng đợc thâm nhập tới lần cuối thì giá trị bên trong mảng sẽ tới trực tiếp từ hình ảnh đĩa Điều này cho mảng kết hợp 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 kết hợp 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 đầ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 nhơ tách biệt trông
Chúng ta có vài toán tử mới ở đây: chu trình foreach, sắp xếp một danh sách,
và lấy khoá của mảng
Trớc hết, toán tử keys() lấy một tên mảng kết hợp nh một đối và cho lại một danh sách tất cả các khoá của mảng đó theo một thứ tự không xác định nào đó (Điều này hệt nh toán tử keys trong awk.) Với mảng %words đợc xác định trớc
Trang 32đây, kết quả là một cái gì đó tựa nh fred, barney, betty, wilma, theo một thứ tự không xác định Với mảng %last_good, kết quả sẽ là một danh sách của tất cả ngời dùng
đã đoán thành công từ bí mật của riêng mình
Toán tử 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 chỉ 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ữ
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
@password = getpwuid($<); # lấy dữ liệu mật hiệu
$name = $password[6]; # lấy trờng GCOS
$name =~ s/,.*//; # vứt đi mọi thứ sau dấu phẩy đầu tiên
if ($name =~ /^randal\b/i ) { # trở lại cách khác
print “Xin chào, Randal! May quá anh ở đây!\n”;
while ( ! &good_word($name, $guess)) {
print “Sai rồi, thử lại đi Từ bí mật là gì?”;
Trang 33while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
close (WORDSLIST);
}
}
sub good_word {
local($somename, $someguess) = @_; # tên tham biến
$somename =~ s/\W.*//; # bỏ mọi thứ sau từ đầu
$somename =~ tr/A-Z/a-z/; # mọi thứ thành chữ thờng
if ($somename eq “randal”) { # không nên đoán
1; #giá trị cho lại là true
} elsif (($words{$somename} || “đồ cáu kỉnh”) eq $someguess) {
1; # giá trị cho lại là true
} else {
open(MAIL, “|mail Địa_chỉ_bạn_ở_đây”);
print MAIL “tin xấu: $somename đã đoán $someguess\n”;
0; # cho lại giá trị false
}
}
Tiếp đó, ta có bộ in từ bí mật:
#! /usr/bin/perl
while ($filename = <*.secret>) {
open (WORDSLIST, $filename);
Trang 34$filename, $name, $word
Bài tập
Thông thờng, mỗi chơng sẽ kết thúc với một số bài tập, lời giải cho chúng sẽ
có trong Phụ lục A, Trả lời bài tập Với chuyến đi dạo này, lời giải đã đợc cho ở trên
1 Hãy gõ trong chơng trình thí dụ trên vào máy rồi cho nó chạy (Bạn cần tạo ra danh sách từ bí mật nữa.) Hãy hỏi thầy Perl của mình nếu bạn cần trợ giúp
Trang 35Dữ liệu vô hớng
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 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, Perl dùng chúng gần nh đổi lẫn cho nhau, cho nên tôi sẽ mô tả chúng với nhau
Một giá trị vô hớng có thể đợc tác động tới qua các toán tử (giống nh phép cộng hay ghép tiếp), nói chung cho lại 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 vô hớng có thể đợc đọc từ tệp
và thiết bị, và đợc ghi ra nữa
Số
Mặc dầu vô hớng thì hoặc là một số hay một xâu, điều cũng có ích là nhìn vào các số và xâu tách biệt nhau trong một chốc ta sẽ xét số trớc rồi đến xâu
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ấuchấm thập phân,
nh 3.14, hay 1.35 lần 1025) Nhng 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), nhng 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ó tẹo nào.
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, nhng 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ự, nhng không đồng nhất.)
Trang 36Perl 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 E Chẳng hạn:
Xâu ngắn nhất có thể đợc thì không có kí tự nào Xâu dài nhất thì chiếm trọn
bộ nhớ của bạn (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 hạn sẵn gì” mà Perl cho phép mọi cơ hội Các xâu điển hình là các dẫy in đợc gồm các chữ và 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á víu lõi UNIX bằng việc đọc nó vào trong xâu Perl, tiến hành thay đổi, và ghi kết quả lại.)
Giống nh số, xâu có 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 vị: xâu dấu nháy đơn và
xâu dấu nháy kép.
* Chỉ báo “số không đứng đầu” chỉ có tác dụng với các hằng kí hiệu - không có tác dụng cho việc chuyển đổi tự
động xâu sang số bạn có thể chuyển đổi một xâu dữ liệu giống nh một giá trị hệ tám và hệ mời sáu thành một số với oct() hay hex().
Trang 37Xâ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 dòng mới, 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 biệt 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 Dới dạng hình ảnh:
‘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 không (không 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, dòng mới, there (toàn bộ 11 kí tự)
Chú ý rằng \n bên trong môt jxâu có nháy đơn thì không đợc hiểu là dòng mới, nhng là hai kí tự sổ chéo ngợc và n (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.)
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 Nhng 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òng mới
“new \177” # new, dấu cách và kí tự xoá (177 hệ tám)
“coke\tsprite” # coke, dấu tab, và sprite
Dấ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 Lối thoát sổ chéo ngợc xâu nháy kép
Trang 38Toá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 nsoi 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
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, vân vân Chẳng hạn:
Trang 392 + 3 # 2 cộng 3, hay 5
5.1 - 2.4 # 5.1 trừ đi 2.4, hay 2.7
3 * 12 # 3 lần 12 = 36
14 / 2 # 14 chia cho 2, hay 7
10.2 / 0.3 # 10.2 chia cho 0.3, hay 34
10 / 3 # bao giờ là phép chia dấu phẩy động, nên 3.333
Bên cạnh đó, Perl cung cấp toán tử lũy thừa kiểu FORTRAN, mà nhiều ngời
đã từng mong mỏi cho Pascal và C Toán tử này đợc biểu diễn bằng hai dấu sao,
nh 2**3, chính là hai luỹ thừa ba, hay tám (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.)
Perl cũng hỗ trợ cho toán tử lấy đồng d modulus, nh trong C Giá trị của biểu thức 10 % 3 là số d khi lấy mời chia cho ba, chính là một Cả hai giá trị đều trớc hết đợ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 hay sai Chẳng hạn,
3 2 cho lại đúng vì ba lớn hơn hai, trong khi 5 != 5 cho lại sai vì không đúng là năm lại không bằng năm Các định nghĩa về đúng và sai đợc nói tới về sau, nhng 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 - một là
đúng, còn không là sai (Các toán tử này sẽ đwojc thăm lại trong Bảng 2-2.)
Toán tử xâu
Các giá trị xâu có thể đợc ghép với toán tử chấm (.) (Quả thế, đó là dấu chấm
đơn.) Điều này không làm thay đổi xâu, cũng nh 2+3 không làm thay đổi 2 hay 3 Xâu kết quả (dài hơn) vậy là có sẵn cho tính toán thêm hay đợc cất giữ trong một biến
“hello” “world” # hệt nh helloworld“ ”
‘hello wordl’ “\n” # hệt nh hello world\n“ ”
“fred” “ “ “barney” # hệt nh fred barney“ ”
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 mà bạn đơn thuần phải đánh dấu hai giá trị gần lẫ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, vân vâ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 40Lớn hơn > gt
Bạn có thể tự hỏi tại sao lại có các toán tử phân tách cho số và xâu vậy, nếu số
và xâu đợc tự động chuyển đổi lẫn cho nhau ta hãy xét hai giá trị 7 và 30 Nếu
đ-ợc so sánh nh số thì 7 hiển nhiên bé hơn 30, nhng 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ứ ờ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 cụ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)
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 Nhng nếu ta làm phép nhân trớc (nh ta vẫn đợc dậy trong giờ toán) thì ta đợc 2+12, hay 14 May mắn là Perl chọn định nghĩa toán học thông th-ờng, thực hiện nhân trớc Bởi ffiều này, ta nói nhân có số u tiên cao hơn cộng.Bạn có thể phá rào trật tự theo số u tiên bằng việc dùng dấu ngoặc Bất kì cái