Learning Perl - Giới thiệu qua về Perl part 3 pps

6 197 0
Learning Perl - Giới thiệu qua về Perl part 3 pps

Đang tải... (xem toàn văn)

Thông tin tài liệu

print "Chao Jenny! Lam bai tap di chu! \n " ; } else { 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? "; $guess = <STDIN>; chop($guess); } #ket thuc cua while } 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 sub init_words { open(WORDSLIST, "wordslist"); while ($name = <WORDSLIST>) { chop($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } close(WORDSLIST); } #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 bạ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: sub init_words { open(WORDSLIST, "wordslist"); if (-M WORDSLIST > 7) { die "Rat tiec, danh sach tu cu hon 7 ngay roi!"; } while ($name = <WORDSLIST>) { chop ( $name ) ; chop ( $name ) ; $word = <WORDSLIST>; chop($word); $words{$name} = $word; } close(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 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 { 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 { sub init_words { while ($filename = <*.secret>) { open (WORDSLIST, $filename); if (-M WORDSLIST <= 7) { while ($name = <WORDSLIST>) { chop($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } #ket thuc while close(WORDSLIST); } #ket thuc if } #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); if (-M WORDSLIST <= 7) { while ($name = <WORDSLIST>) { chop($name); $word = <WORDSLIST>; chop($word); #[Phần mới sẽ được đưa vào đây] (*) $words{$name} = $word; } #ket thuc while close(WORDSLIST); } #ket thuc if } #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 Ở (*) 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 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): format STDOUT = @<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<< $filename, $name, $word . Định nghĩa định dạng này bắt đầu với format STDOUT =, và kết thúc với một dấu chấm (.). Hai dòng ở giữa là phần định dạng. Dòng đầu của định dạng này là dòng định nghĩa các trường sẽ được in ra, 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: while ($filename = <*.secret>) { open (WORDSLIST, $filename); if (-M WORDSLIST <= 7) { while ($name = <WORDSLIST>) { chop($name); $word = <WORDSLIST>; chop($word); write; #<== phan moi $words{$name} = $word; } #ket thuc while close(WORDSLIST); } #ket thuc if } #ket thuc while format STDOUT = @<<<<<<<<<<<<<<< @<<<<<<<<<< @<<<<<<<<<<<<<< $filename, $name, $word . Khi định dạng này được gọi tới, thì Perl sẽ tính các biểu thức trường và sinh ra một dòng mà nó gửi ra tước hiệu tệp STDOUT. Vì write được gọi một lần mỗi khi đi qua chu trình nên ta sẽ thu được một loạt các dòng với văn bản theo cột, mỗi dòng cho một từ 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 @<< $% Ten tep Ten Tu bi mat ========== ======== ========== . Định dạng này mang tên STDOUT_TOP, và sẽ được dùng đầu tiên ngay khi gọi tới định dạng STDOUT, rồi lại sau 60 lần đưa ra STDOUT thì nó lại được sinh ra. Các tiêu đề cột ở đây thẳng hàng với các cột trong định dạng STDOUT, cho nên mọi thứ "khớp" vào với nhau thật đẹp! Dò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 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 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); if (-M WORDSLIST <= 7) { while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } } else { #doi ten tep de no "an tuong" hon 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 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 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}; $hours = (time - $when) / 3600; #tinh gio da qua write; } dbmclose (%last_good); format STDOUT = User @<<<<<<<<<<: lan doan dung cuoi cung la @<<< gio da qua. $name, $hour . 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, hàm keys() lấy một tên mảng băm như một tham số và trả về 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 đây, kết quả là một cái gì đó giông giống như Dale, Chip, Tom, Jerry 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. 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. 1.7 Bài tập Chương này, và hầu hết các chương sau đó, sẽ có phần bài tập để bạn ôn luyện lại những gì đã học được. Bài tập của chương này thật đơn giản: bạn hãy gõ lại các chương trình ở trên vào đĩa và thử chạy chúng (đừng quên tạo thêm các tệp từ bí mật nữa nhé) Mục lục »» Chương 2 . này: #!/usr/bin /perl dbmopen (%last_good, "lastdb", 0666); foreach $name (sort keys(%last_good) { $when = $last_good{$name}; $hours = (time - $when) / 36 00; #tinh gio da qua write; } dbmclose (%last_good); format. 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,. 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

Ngày đăng: 26/07/2014, 12:20

Tài liệu cùng người dùng

Tài liệu liên quan