perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/hoc-perl.pdf · học...

354
Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

Upload: others

Post on 01-Sep-2019

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

Học

Perl

Randal L. Schwartz

Người dịch: Ngô Trung Việt

Hà Nội 5/1999

Page 2: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

Learning

Perl

Randal L. Schwartz

O’Reilly & Associates, Inc., 1993

Page 3: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

i

Mục lục

1 Giới thiệu .................................................................. 1

Lịch sử Perl ................................................................. 1

Mục đích của Perl ....................................................... 3

Tính sẵn có .............................................................. 3

Hỗ trợ ...................................................................... 5

Các khái niệm cơ bản .................................................. 6

Dạo qua Perl ................................................................ 9

Chương trình “Xin chào mọi người” .................... 10

Hỏi câu hỏi và nhớ kết quả ................................... 11

Bổ sung chọn lựa .................................................. 12

Đoán từ bí mật ...................................................... 13

Nhiều từ bí mật ..................................................... 14

Cho mỗi người một từ bí mật khác nhau .............. 16

Giải quyết dạng thức cái vào thay đổi .................. 19

Làm cho công bằng với mọi người ....................... 21

Làm cho nó mô đun hơn một chút ........................ 24

Chuyển danh sách từ bí mật vào tệp riêng biệt ..... 28

Đảm bảo một lượng an toàn giản dị ...................... 33

Cảnh báo ai đó khi mọi việc đi sai ........................ 34

Nhiều tệp từ bí mật trong danh mục hiện tại ........ 36

Nhưng chúng ta biết họ là ai! ................................ 38

Liệt kê các từ bí mật .............................................. 40

Page 4: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

ii

Làm cho danh sách từ cũ đó đáng lưu ý hơn ........ 44

Duy trì cơ sở dữ liệu đoán đúng cuối cùng ........... 45

Chương trình cuối cùng ........................................ 48

Bài tập ....................................................................... 51

2 Dữ liệu vô hướng .................................................... 53

Dữ liệu vô hướng là gì? ............................................ 53

Số .......................................................................... 54

Tất cả các số đã có cùng dạng thức bên trong ...... 54

Hằng kí hiệu động ................................................. 54

Hằng kí hiệu nguyên ............................................. 55

Xâu ............................................................................ 56

Xâu dấu nháy đơn ................................................. 56

Xâu dấu nháy kép ................................................. 57

Toán tử ...................................................................... 59

Toán tử số ............................................................. 59

Toán tử xâu ........................................................... 61

Thứ tự ưu tiên và luật kết hợp của toán tử ............ 63

Chuyển đổi giữa số và xâu .................................... 66

Các toán tử trên biến vô hướng ............................. 68

Toán tử gán hai ngôi ............................................. 69

Tự tăng và tự giảm ................................................ 71

Toán tử chop() ....................................................... 72

Xen lẫn vô hướng vào trong xâu ........................... 73

<STDIN> xem như một vô hướng ....................... 75

Đưa ra bằng print() ............................................... 76

Giá trị undef ......................................................... 77

Bài tập ....................................................................... 78

3 Dữ liệu mảng và danh sách ................................... 79

Page 5: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

iii

Mảng là gì? ............................................................... 79

Biểu diễn hằng kí hiệu .......................................... 80

Biến ....................................................................... 81

Toán tử .................................................................. 82

Phép gán ................................................................ 82

Truy nhập phần tử ................................................. 85

Các toán tử push() và pop() .................................. 88

Các toán tử shift() và unshift() .............................. 89

Toán tử reverse() ................................................... 89

Toán tử sort() ........................................................ 90

Toán tử chop() ....................................................... 90

Hoàn cảnh vô hướng và mảng .............................. 91

<STDIN> như một mảng ...................................... 92

Xen lẫn biến mảng ................................................ 92

Bài tập ....................................................................... 94

4 Cấu trúc điều khiển ................................................ 97

Khối câu lệnh ............................................................ 97

Câu lệnh if/unless .................................................. 98

Câu lệnh while/until ............................................ 101

Câu lệnh for ........................................................ 103

Câu lệnh foreach ................................................. 104

Bài tập ..................................................................... 106

5 Mảng kết hợp........................................................ 109

Mảng kết hợp là gì? ................................................ 109

Biến mảng kết hợp .............................................. 110

Biểu diễn hằng kí hiệu cho mảng kết hợp ........... 111

Các toán tử mảng kết hợp ....................................... 112

Page 6: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

iv

Toán tử keys() ..................................................... 112

Toán tử values() .................................................. 113

Toán tử each() ..................................................... 114

Toán tử delete ..................................................... 114

Bài tập ..................................................................... 115

6 Vào / ra cơ bản ..................................................... 117

Vào từ STDIN ......................................................... 117

Đưa vào từ toán tử hình thoi ............................... 119

Đưa ra STDOUT ..................................................... 120

Dùng print cho đưa ra thông thường ................... 120

Dùng printf cho cái ra có dạng thức .................... 121

Bài tập ..................................................................... 122

7 Biểu thức chính qui .............................................. 123

Khái niệm về biểu thức chính qui ........................... 123

Cách dùng đơn giản về biểu thức chính qui........ 124

Khuôn mẫu .............................................................. 126

Khuôn mẫu một kí tự .......................................... 127

Khuôn mẫu nhóm ................................................ 129

Dấu ngoặc tròn như bộ nhớ................................. 132

Thay phiên .......................................................... 134

Khuôn mẫu neo ................................................... 134

Thứ tự ưu tiên.......................................................... 136

Thêm về toán tử đối sánh ........................................ 137

Chọn một mục tiêu khác (toán tử =~) ................. 137

Bỏ qua chữ hoa thường ....................................... 138

Dùng một định biên khác .................................... 139

Page 7: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

v

Dùng xen lẫn biến ............................................... 140

Biến chỉ đọc đặc biệt ........................................... 141

Thay thế .................................................................. 142

Các toán tử split() và join() ..................................... 144

Toán tử split() ..................................................... 144

Toán tử join() ...................................................... 146

Bài tập ..................................................................... 146

8 Hàm ....................................................................... 149

Hàm hệ thống và hàm người dùng .......................... 149

Xác định một hàm người dùng ........................... 149

Gọi một hàm người dùng .................................... 151

Giá trị cho lại ...................................................... 152

Đối ...................................................................... 153

Biến cục bộ trong hàm ........................................ 156

Bài tập ..................................................................... 159

9 Các cấu trúc điều khiển khác .............................. 161

Toán tử last ......................................................... 161

Toán tử next ........................................................ 163

Toán tử redo ........................................................ 164

Khối có nhãn ....................................................... 165

Bộ thay đổi biểu thức .......................................... 167

&&, || và ?: xem như các cấu trúc điều khiển ..... 169

Bài tập ..................................................................... 171

10 Tước hiệu tệp và kiểm thử tệp .......................... 173

Tước hiệu tệp là gì?................................................. 173

Page 8: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

vi

Mở và đóng một tước hiệu tệp ................................ 174

Một chút tiêu khiển: die() ................................... 175

Dùng tước hiệu tệp .................................................. 177

Kiểm tra tệp -x .................................................... 178

Các toán tử stat() và lstat() .................................. 183

Dùng _Filehandle ................................................ 184

Bài tập ..................................................................... 185

11 Dạng thức ............................................................ 187

Dạng thức là gì? ...................................................... 187

Định nghĩa một dạng thức ................................... 188

Gọi một dạng thức .............................................. 191

Nói thêm về nơi giữ tệp ...................................... 193

Trường văn bản ................................................... 194

Trường số ............................................................ 194

Trường nhiều dòng.............................................. 196

Trường được lấp đầy ........................................... 196

Dạng thức đầu trang ............................................ 200

Thay đổi mặc định cho dạng thức ........................... 201

Dùng select() để thay đổi tước hiệu tệp .............. 201

Thay đổi tên dạng thức ....................................... 203

Đổi tên dạng thức đầu trang ................................ 204

Đổi chiều dài trang .............................................. 205

Thay đổi vị trí trên trang ..................................... 206

Bài tập ..................................................................... 207

12 Truy nhập danh mục ......................................... 209

Chuyển vòng quanh cây danh mục ......................... 209

Globbing ................................................................. 210

Page 9: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

vii

Tước hiệu danh mục ........................................... 213

Mở và đóng tước hiệu danh mục ........................ 214

Đọc một tước hiệu danh mục .............................. 215

Bài tập ..................................................................... 216

13 Thao tác tệp và danh mục ................................. 217

Loại bỏ tệp .............................................................. 217

Đổi tên tệp ............................................................... 219

Tạo ra tên thay phiên cho một tệp (móc nối) ...... 220

Về móc nối cứng và mềm ................................... 220

Tạo ra các móc nối cứng và mềm bằng Perl ....... 222

Tạo ra và xoá danh mục ...................................... 224

Thay đổi phép sử dụng ........................................ 225

Thay đổi quyền sở hữu........................................ 226

Thay đổi nhãn thời gian ...................................... 227

Bài tập ..................................................................... 229

14 Quản lí tiến trình ................................................ 230

Dùng system() và exec() ......................................... 230

Dùng dấu nháy đơn ngược .................................. 235

Dùng các tiến trình như tước hiệu tệp ................. 236

Dùng fork ............................................................ 238

Tóm tắt về các phép toán tiến trình ..................... 241

Gửi và nhận tín hiệu ............................................ 243

Bài tập ..................................................................... 246

15 Biến đổi dữ liệu khác ......................................... 249

Tìm một xâu con ..................................................... 249

Trích và thay thế một xâu con............................. 251

Page 10: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

viii

Dạng thức dữ liệu bằng sprintf() ......................... 254

Sắp xếp nâng cao .................................................... 254

Chuyển tự ................................................................ 260

Bài tập ..................................................................... 263

16 Truy nhập cơ sở dữ liệu hệ thống ..................... 265

Lấy mật hiệu và thông tin nhóm ............................. 265

Gói và mở dữ liệu nhị phân..................................... 270

Lấy thông tin mạng ................................................. 273

Lấy các thông tin khác ............................................ 275

Bài tập ..................................................................... 276

17 Thao tác cơ sở dữ liệu người dùng .................... 277

Cơ sở dữ liệu DBM và mảng DBM ........................ 277

Mở và đóng mảng DBM ..................................... 278

Dùng mảng DBM ................................................ 280

Cơ sở dữ liệu truy nhập ngẫu nhiên chiều dài cố

định ..................................................................... 281

Cơ sở dữ liệu (văn bản) chiều dài thay đổi ......... 284

Bài tập ..................................................................... 287

18 Chuyển đổi các ngôn ngữ khác sang Perl ......... 289

Chuyển chương trình awk sang Perl ................... 289

Chuyển đổi chương trình sed sang Perl .............. 291

Chuyển đổi chương trình Shell sang Perl ........... 292

Bài tập ..................................................................... 293

Phụ lục A Trả lời các bài tập .................................. 295

Page 11: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

ix

Chương 2, Dữ liệu vô hướng .............................. 295

Chương 3, Mảng và dữ liệu danh sách ............... 297

Chương 4, Cấu trúc điều khiển ........................... 299

Chương 5, Mảng kết hợp .................................... 302

Chương 6, Vào/ra cơ sở ...................................... 305

Chương 7, Biểu thức chính qui ........................... 307

Chương 8, Hàm ................................................... 311

Chương 9, Các cấu trúc điều khiển khác ............ 314

Chương 10, Tước hiệu tệp và kiểm thử tệp ........ 315

Chương 11, Dạng thức ........................................ 317

Chương 12, Truy nhập danh mục ....................... 319

Chương 13, Thao tác tệp và danh mục ............... 320

Chương 14, Quản lí tiến trình ............................. 323

Chương 15, Biến đổi dữ liệu khác ...................... 326

Chương 16, Truy nhập cơ sở dữ liệu hệ thống ... 329

Chương 17, Thao tác cơ sở dữ liệu người dùng.. 330

Chương 18, Chuyển các ngôn ngữ khác sang Perl

............................................................................ 332

Phụ lục B Cơ sở về nối mạng .................................. 333

Mô hình khe cắm ................................................ 333

Một khách mẫu ................................................... 335

Bộ phục vụ mẫu .................................................. 335

Phụ lục C Các chủ đề còn chưa nói tới .................. 337

Trình gỡ lỗi ......................................................... 337

Dòng lệnh ............................................................ 338

Các toán tử khác .................................................. 338

Nhiều, nhiều hàm nữa ......................................... 338

Nhiều, nhiều biến định nghĩa sẵn ........................ 338

Xâu ở đây ............................................................ 338

Trở về (từ trình con)............................................ 339

Page 12: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

x

Toán tử eval (và s///e) ......................................... 339

Thao tác bảng kí hiệu bằng *FRED .................... 340

Toán tử goto ........................................................ 340

Toán tử require .................................................... 341

Thư viện .............................................................. 341

Tin vui về Perl 5.0 ............................................... 341

Page 13: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

1

1

Giới thiệu

Lịch sử Perl

Perl là cách viết tắt cho “Practical Extraction and

Report Language” Ngôn ngữ báo cáo và trích rút thực

hành, mặc dầu nó cũng còn được gọi là “Pathologically

Eclectic Rubbish Lister” - Bộ liệt kê rác điện tử bệnh

hoạn. Chẳng ích gì mà biện minh xem cách gọi nào đúng

hơn, vì cả hai đều được Larry Wall, người sáng tạo và

kiến trúc sư chính, người cài đặt và bảo trì của Perl chấp

nhận. Ô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 ở 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à ông 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.

Sau 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

Trong chương này: Lịch sử Perl Mục đích của Perl Có sẵn Hỗ trợ Các khái niệm cơ bản

Dạo qua về Perl

Page 14: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

2

giả Usenet, thường vẫn được gọi là “the Net”. Người

dùng thuộc toán người 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 ấy 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 chưa bao giờ mường

tượng ra về việc giải quyết cho Perl nhỏ bé của mình cả.

Nhưng 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ể 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 đó sẽ làm việc

với bản 5.0 và các bản sau của Perl. Thực ra, 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 là cần cho sự tiến bộ.

Page 15: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

3

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 việc làm ầm ĩ) một

số giải pháp có ưu thế rất trầm 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. Nhưng với chú ý đúng đắn, bạn có thể tránh được

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, nhưng 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 ta

có lẽ sẽ 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

Page 16: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

4

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. Nhưng 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 trưa không mất

tiền”).

Perl được phân phối theo giấy phép công khai 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 cho 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ư tưởng đặc biệt về “cấu hình phần cứng

được hỗ trợ.”

Thực ra, nó không chỉ là cho không, nhưng 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ó sang 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

Page 17: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

5

độ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

đã được sửa chữa trong các lần đưa ra sau, nhưng 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 đã 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, nhưng chưa 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

[email protected], 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ướng 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

Page 18: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

6

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 đã có trả lời

trong vòng vài phút khi tin của bạn tới đầu nối Usenet

chính. 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 & Associates, 1990).

Programming Perl được xem như “Sách con lạc đà” 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ạc đà chứa thông tin tham chiếu đầy đủ về Perl dưới

dạng đóng gọn gàng. Sách con lạc đà cũng bao gồm một

bảng tra tham chiếu 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

chmod +x filename) và rồi gõ tên của tệp đó vào lời nhắc

Page 19: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

7

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 $ cat somescript date who $ chmod _x somescript $ somescript [output of date followed by who] $

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. Nhưng 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ỏ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,

Page 20: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

8

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 đó, nhưng 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 đã 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 đã 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ỏ (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 biến mất và

sẽ không làm chậm chương trình. Thực ra, 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ệ

Page 21: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

9

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 của Larry cho Perl tương lai.

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ủ đề đã được thảo luận chi tiết hơn rất nhiều

về sau trong cuốn sách này. Nhưng 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.

Page 22: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

10

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 -

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 đã 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 đối của nó ra lối ra. Sau khi

chương trình đã hoàn tất, tiến trình Perl ra, cho lại một

mã ra thành công cho lớp vỏ.

* 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.

Page 23: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

11

Hỏi câu hỏi và nhớ kết quả

Thêm một chút phức tạp hơn. Từ Xin chào mọi người

là một đụng chạm lạnh nhạt và cứng rắn. Làm cho

chương trình gọi bạn theo tên bạn. Để làm việc này, 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 đó,

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. Gán cái vào này cho biến $name. Điều này cho

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ì cần làm là nói Xin chào, tiếp

Page 24: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

12

đó là giá trị của biến $name, mà 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 muốn một dấu đô la thay vì

tham chiếu biến vô hướng, thì có thể đặt trước dấu đô la

với một dấu sổ chéo ngược.

Gắn tất cả lại, ta được:

#!/usr/bin/perl print “Tên bạn là gì? ”; $name = <STDIN> ; chop($name); print “Xin chào, $name!\n”;

Bổ sung chọn lựa

Bây giờ ta muốn có một lời chào đặc biệt cho

Randal, 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, cần so sánh tên đã được

đưa vào với xâu Randal, và nếu hai xâu là một, thì làm

điều gì đó đặc biệt. Bổ sung thêm lệnh rẽ nhánh if-then-

else và phép so sánh vào chương trình:

#!/usr/bin/perl print “Tên bạn là gì? ”; $name = <STDIN> ; chop($name); if ($name eq “Randal”) { print “Xin chào Randal! Tốt quá anh lại ở đây!\n”; } else { print “Xin chào, $name!\n”; # chào mừng thông

thường }

Page 25: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

13

Toán tử eq so sánh hai xâu. Nếu chúng bằng nhau

(từng kí tự một, và có cùng chiều dài), thì kết quả là

đúng. (Không có toán tử này trong C, và awk phải dùng

cùng toán tử cho xâu và số và tạo ra việc đoán có rèn

luyện.)

Câ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 để 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 xem chương trình này và rồi xem

giải thích:

#! /usr/bin/perl $secretword = “llama”; # từ bí mật print “Tên bạn là gì?” ; $name = <STDIN> ; chop($name); if ($name eq “Randal”) { print “Xin chào, Randal! May quá anh ở đây!\n”; } else { print “Xin chào, $name!\n”; # chào thông thường print “Từ bí mật là gì?” ; $guess = <STDIN>; chop($guess); while ($guess ne $secrectword) { print “Sai rồi, thử lại đi. Từ bí mật là gì?”; $guess = <STDIN>;

Page 26: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

14

chop($guess); } }

Trước hết, đị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 mộ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 đã 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ừ. Nhưng, chúng ta hiện tại chưa đị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 đã 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

Page 27: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

15

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 đã là một biến vô

hướng tách biệt mà có thể được đặt giá trị hay truy 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ể truy nhập

vào từng phần tử bằng việc dùng một tham chiếu chỉ số.

Cho nên $words[0] là camel, $words[1] là llama, $words[2]

là oyster. 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à oyster. (Tham chiếu chỉ

số bắt đầu với $ thay vì @ vì chúng tham chiếu 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 $words = (“camel”, “llama”, “oyster”); # từ bí mật print “Tên bạn là gì?” ; $name = <STDIN> ; chop($name); if ($name eq “Randal”) { print “Xin chào, Randal! May quá anh ở đây!\n”; } else { print “Xin chào, $name!\n”; # chào thông thường print “Từ bí mật là gì?” ; $guess = <STDIN>; chop($guess); $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

Page 28: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

16

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 } else # hết rồi, thế là hỏng print “Sai rồi, thử lại đi. Từ bí mật là gì?”; $guess = <STDIN>; chop($guess); $i = 0; # bắt đầu kiểm tra từ đầu lần nữa } } # 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, nhưng 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

đã 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ừ:

Page 29: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

17

Người Từ bí mật

Fred

Barney

Betty

Wilma

camel

llama

oyster

oyster

Chú ý rằng cả Betty và Wilma đã 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), nhưng các mảng lại được tham chiếu 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à giá 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 đã 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ư

Page 30: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

18

khoá trong một tham chiếu 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 chiếu 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ì?” ; $name = <STDIN> ; chop($name); if ($name eq “Randal”) { print “Xin chào, Randal! May quá anh ở đây!\n”; } else { 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”

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

* Được, đấy chính là giá trị undef, nhưng nó trông như một xâu

rỗng cho toán tử eq

Page 31: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

19

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 $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 dạng thức 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 khuôn mẫu 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:

if ($name =~ /^Randal/) { ## có, sánh đúng } else { ## không, sánh sai }

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, dấu cách và

Page 32: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

20

khoảng trắng là có nghĩa, hệt như chúng ở bên trong xâu.

Điều này gần như thế, nhưng 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ì?” ; $name = <STDIN> ; chop($name); if ($name =~ /^randal\b/i ) { print “Xin chào, Randal! May quá anh ở đây!\n”; } else { print “Xin chào, $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) {

Page 33: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

21

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, nhưng 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ế, nhưng 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 tinh 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 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, nhưng 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

Page 34: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

22

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 đó, 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 tá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

Page 35: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

23

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, và cứ 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

* Các chuyên gia sẽ lưu ý 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,

nhưng các chuyên gia có lẽ sẽ không đọc mục này.

Page 36: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

24

} # kết thúc của “not Randal”

Để ý đế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 đã 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 đã 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 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 có thể

làm tham chiếu 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 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 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ừ đã

Page 37: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

25

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ỏ nhưng tăng trưởng nhanh của

chúng ta, tạo ra một chương trình con tên là &good_word

(tất cả các tên chương trình con đã 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 } }

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 (không có dấu và &) tiếp nữa là một khối mã

Page 38: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

26

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,

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à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, nhưng ngay cả nếu nó có, thì dù từ nào được đoán

cũng đã 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 - 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

Page 39: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

27

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 đã 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ì cho lại 1, nếu không cho lại 0.

Bây giờ 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 print “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,

Page 40: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

28

chừng nào còn liên quan tới chương trình chính.

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. (Nhưng 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, nhưng 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ì đã là toàn cục đối với toàn bộ

chương trình. Thực ra, đâ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 lâu.

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

Page 41: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

29

ba chương trình. Nếu cất giữ danh sách từ như đã làm thì

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, đặ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, 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 đó:

sub init_words { open (WORDSLIST, “wordslist”); while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } close (WORDSLIST); }

Tôi đặt nó vào một chương trình con để cho tôi có

thể giữ phần chính của chương trình không bị lộn xộn.

Điều này cũng có nghĩa là vào thời điểm sau (hướng dẫn:

một vài lần ôn lại cuộc đi dạo này), tôi có thể thay đổi

nơi cất giữ danh sách từ, hay thậm chí định dạng của

Page 42: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

30

danh sách.

Đị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:

fred camel barney llama betty oyster wilma oyster

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. Lưu ý 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 đã đượ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

* Về mặt kĩ thuật thì đấy lại là undef, nhưng cũng đủ gần cho thảo

luận này

Page 43: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

31

$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ó nữ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ể 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(). (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,

nhưng 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 quát tất cả những điều

này có thể giống thế này:

#! /usr/bin/perl &init_words; 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 print “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);

Page 44: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

32

} } ## chương trình con từ đây xuống sub init_words { open (WORDSLIST, “wordslist”); while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } 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 { 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.)

Page 45: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

33

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, 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.

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 trướ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.”; } while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; }

Page 46: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

34

close (WORDSLIST); }

Giá trị của -M WORDSLIST được so sánh với bẩy,

và nếu lớn hơn, thế thì ta vi phạm vào đường lối rồi. Tại

đây, ta thấy một toán tử mới, toán tử die, mà cho in ra

mộ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 truy 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

Xem có thể làm cho hệ thống bị sa lầy thế nào khi

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. Cần sửa đổi mỗi chương trình

con &good_word (nhờ có tính mô đun) vì 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 phải làm - ngay trước khi trả 0 về từ

chương trình con, 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 {

* Thực tại là STDERR, nhưng đấy thông thường là màn hình

Page 47: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

35

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(), 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 đ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 đang mở

một chỉ lệnh để 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, đóng tước hiệu tệp, mà sẽ bắt đầu để

mail gửi dữ liệu của nó theo cách của nó.

* 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.

Page 48: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

36

Để cho đúng, chúng ta có thể gửi câu trả lời đúng

cũng như câu trả lời sai, nhưng 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

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ì 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ỏ, nói:

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ự.

lấy lại định nghĩa &init_words :

sub init_words { while ($filename = <*.secret>) { open (WORDSLIST, $filename); if (-M WORDSLIST > 7) { while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>;

Page 49: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

37

chop($word); $words{$name} = $word; } } close (WORDSLIST); } }

Trước hết, tôi đã bao một chu trình while mới quanh

phần lớn chương trình con của bản cũ. Điều mới ở đây là

toán tử <*.secret>. Điều này được gọi là núm tên tệp, bởi

lí do lịch sử. Nó làm việc rất giống <STDIN>, ở chỗ mỗi

lần được truy nhập tới, nó cho lại giá trị tiếp: tên tệp kế

tiếp sánh với mẫu của vỏ, trong trường hợp này là

*.secret. Khi không có tên tệp thêm nữa được cho lại thì

núm tên tệp cho xâu rỗng* .

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 bả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 như

trước.

Chú ý rằng nếu không có tệp nào sánh với *.secret và

lại ít hơn bẩy ngày thì chương trình con sẽ ra 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ừ đồ cáu kỉnh. Thế cũng

* Lại undef lần nữa

Page 50: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

38

được. (Với mã thật, tôi sẽ thêm một kiểm tra nào đó về

số các ô trong %words trước khi cho lại, và die nếu nó

không tốt. Xem toán tử keys() khi lấy mảng kết hợp trong

Chương 5, Mảng kết hợp.)

Nhưng chúng ta biết họ là ai!

Được rồi, chúng ta đã hỏi tên người dùng khi thực ra

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 đá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

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

Page 51: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

39

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 print “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); } }

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; } } close (WORDSLIST); } }

sub good_word { local($somename, $someguess) = @_; # tên tham

biến

Page 52: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

40

$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 } }

Chú ý rằng không còn cần hỏi người dùng về tên

người ấy nữa - chúng ta đã biết nó!

Perl cung cấp nhiều trình truy nhập cơ sở dữ liệu hệ

thống để lôi ra những giá trị từ cơ sở dữ liệu mật hiệu,

nhóm, máy chủ, mạng, dịch vụ và giao thức. Cả việc tra

cứu riêng (như được trình bầy ở trên) và việc duyệt số

lớn cũng đã được Perl hỗ trợ.

Liệ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 gạt sang bên chương trình từ

bí mật một lúc, 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, 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>) {

Page 53: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

41

open (WORDSLIST, $filename); if (-M WORDSLIST > 7) { while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); ### chất liệu mới sẽ đưa vào đây } } close (WORDSLIST); }

Tại điểm có đánh dấu “chất liệu mới sẽ đưa vào

đây,” cần 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. Định nghĩa một dạng thức ở đâ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 dạng thức 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à chính dạng thức. Dòng đầu của dạng thức này là

dòng định nghĩa trường, xác định số lượng, chiều dài và

kiểu của trường. Với dạng thức này, chúng 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 dạng thức 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 đó.

Page 54: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

42

Gọi dạng thức 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>) { chop($name); $word = <WORDSLIST> chop($word); write; # gọi dạng thức STDOUT tới STDOUT } } close (WORDSLIST) }

format STDOUT = @<<<<<<<<<<<<<<< @<<<<<<<<<<

@<<<<<<<<<<<<<< $filename, $name, $word .

Khi dạng thức này được gọi tới, 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 là được gọi một lần mỗi khi đi

qua chu trình nên 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.

Hừm. Chúng còn chưa có nhãn cho các cột. Mà điều

đó thì cũng dễ thôi, chỉ cần thêm vào dạng thức trên đầu

trang, như:

format STDOUT_TOP = Page @<< $% Tên tệp Tên Từ ========== ======== ========== .

Page 55: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

43

Dạng thức này mang tên STDOUT_TOP, và sẽ

được dùng đầu tiên ngay khi gọi tới dạng thức 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 dạng

thức STDOUT, cho nên mọi thứ khớp vào nhau.

Dòng đầu tiên trong dạng thức 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 dạng thức đầu trang.

Dòng thứ ba của dạng thức 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 dạng thức 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 dạng thức 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 dạng thức đầ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 được rót kín. Sẽ có nhiều

vấn đề về điều này hơn khi đi vào các dạng thức trong

Chương 11, Dạng thức.

Page 56: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

44

Làm cho danh sách từ cũ đó đáng lưu ý hơn

Khi đọc qua các tệp *.secret trong danh mục hiện tại,

có thể tìm thấy các tệp quá cũ. Cho tới nay, ta thường

nhảy qua những tệp này. Nay đi thêm một bước nữa - sẽ

đổi tên chúng thành *.secret.old để cho ls của danh mục sẽ

nhanh chóng cho 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); if (-M WORDSLIST > 7) { while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); $words{$name} = $word; } } else { # đổi tên tệp để nó đáng để ý hơn rename($filename, “$filename.old”); } close (WORDSLIST); } }

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, 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

Page 57: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

45

trong chương trình C, bạn cũng có thể làm từ Perl.

Duy trì cơ sở dữ liệu đoán đúng cuối cùng

Cần 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 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 có tên với khoá đó. Qua thời gian, điều

này sẽ dường như cho 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ì 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 đó

Page 58: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

46

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 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 truy 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 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 vẫn không có cách nào để xem xét thông tin

trong đó. Để làm điều đó, có thể tạo ra một chương trình

nhỏ 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; # tính giờ đã qua write; } format STDOUT = User @<<<<<<<<<<: lần đoán đúng cuối cùng là @<<<

giờ đã qua. $name, $hour

Page 59: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

47

.

Chúng 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 đâ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 dạng thức STDOUT, và rồi gọi tới dạng thức

đó. Chú ý rằng chúng đ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.

Page 60: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

48

Chương trình cuối cùng

Sau đây là chương trình mà cuộc đi dạo này đã đưa

tới dưới dạng cuối cùng để bạn có thể chơi với chúng.

Trước hết là chương trình “nói lời chào”:

#! /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 print “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); } }

dbmopen(%last_good, “lastdb”, 066); $last_good($name) = time; dbmclose(%last_good);

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;

Page 61: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

49

} } 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 đó, có bộ in từ bí mật:

#! /usr/bin/perl while ($filename = <*.secret>) { open (WORDSLIST, $filename); if (-M WORDSLIST > 7) { while ($name = <WORDSLIST>) { chop ($name); $word = <WORDSLIST>; chop($word); write; # gọi dạng thức STDOUT cho

STDOUT } } close(WORDSLIST);

Page 62: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

50

}

format STDOUT = @<<<<<<<<<<<<<<< @<<<<<<<<<<

@<<<<<<<<<<<<<< $filename, $name, $word .

format STDOUT_TOP = Page @<< $% Tên tệp Tên Từ ========== ======== ========== .

Và cuối cùng, là chương trình hiển thị từ đã được

dùng lần cuối cùng:

#!/usr/bin/perl dbmopen(%last_good, “lastdb”, 0666); foreach $name (sort keys(%last_good) { $when = $last_good{$name}; $hours = (time - $when) / 3600; # tính giờ đã qua write; } format STDOUT = User @<<<<<<<<<<: lần đoán đúng cuối cùng là @<<<

giờ đã qua. $name, $hour .

Cùng với danh sách từ bí mật (các tệp có tên

something.secret trong danh mục hiện tại) và cơ sở dữ

liệu lastdb.dir và lastdb.pag, bạn đã có tất cả những gì

mình cần.

Page 63: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

51

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. Gõ 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ỏi thầy

Perl của bạn nếu bạn cần trợ giúp.

Page 64: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

52

Page 65: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

53

2

Dữ 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 bởi 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.

Trong chương này:

Dữ liệu vô hướng là gì?

Số

Xâu

Toán tử

Biến vô hướng

Toán tử trên biến vô hướng

<STDIN> xem như giá trị vô hướng

In ra với print()

Giá trị undef

Page 66: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

54

Số

Mặc dầu vô hướng 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ố đã có cùng dạng thức 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 lần 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 để ý (hay quan tâm nhiều) đến việc chuyển

đổi, 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ó 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, 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 đã được xử lí

Page 67: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

55

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 đã đượ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:

1.25 # một phần tư 7.25e45 # 7.25 lần 10 mũ 45 (một số lớn) -6.5e24 # âm 6.5 lần 10 mũ 24 (một số âm lớn) -12e-24 # âm 12 lần 10 mũ -24 (một số âm rất nhỏ) -1.2E-23 # một cách khác để nói điều đó.

Hằng kí hiệu nguyên

Các hằng kí hiệu nguyên cũng là trực tiếp, như

trong:

12 15 -2004 3485

Bạn đừng bắt đầu một số bằng 0, vì Perl hỗ trợ cho

hằng kí hiệu hệ 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

bắt đầu bằng 0x hay 0X* . Các chữ số hệ mười sáu A đến

F (trong cả hai kiểu chữ hoa thường) đã biểu thị cho các

giá trị số qui ước từ 10 đến 15. Chẳng hạn:

0377 # 377 hệ tám, giống như 255 thập phân

* 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().

Page 68: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

56

-0xff # FF hệ mười sáu âm, hệt như -255 thập phân

Xâu

Xâu là các dẫy kí tự (như Xin chào). Mỗi kí tự đã là

một giá trị 8-bit trong toàn bộ tập 256 kí tự (không có gì

đặc biệt về kí tự NUL như trong C).

Xâu ngắn nhất có thể được thì không có kí tự nào.

Xâu dài nhất 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” 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,

quét 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.

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

Page 69: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

57

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 ngoại lệ: để lấy được một dấu nháy đơn trong một

xâu có nháy đơn, đặ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, đặt trước dấu sổ chéo ngược mộ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 xâu có nháy đơn thì

không được hiểu là dòng mới, nhưng 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. 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òng mới

Page 70: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

58

“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

Kết cấu Ý nghĩa

\n dòng mới

\r quay lại

\t tab

\f kéo giấy

\b Xoá lùi

\v tab chiều đứng

\a Chuông

\e lối thoát

\007 bất kì giá trị ASCII hệ tám (ở

đây, 007 = chuông)

\x7f giá trị ASCII hệ mười sáu (ở

đây, 7f = xoá)

\cC bất kì kí tự “điều khiển” nào (ở

đây, control C)

\\ sổ chéo ngược

\” dấu nháy kép

\l chữ tiếp là chữ thường

\L tất cả các chữ đi sau cho tới \E

đã là chữ thường

\u Chữ tiếp là chữ hoa

\U tất cả các chữ đi sau cho tới \E

đã là chữ hoa

\E Kết thúc \L hay \U

Page 71: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

59

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 đã 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 cuộc đi dạo), cho nên tôi sẽ quay lại vấn

đề này sau.

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 đã 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ử 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:

2 + 3 # 2 cộng 3, hay 5

Page 72: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

60

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ử luỹ 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ị đã

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, nhưng với

hiện tại, 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ẽ

được thăm lại trong Bảng 2-2.)

Page 73: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

61

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 đã 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.

Bảng 2-2. Các toán tử so sánh số và xâu

Phép so sánh Số Xâu

Bằng == eq

Không bằng != ne

BĐ hơn < lt

Lớn hơn > gt

BĐ hơn hay bằng <= le

Lớn hơn hay bằng >= ge

Page 74: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

62

Bạn có thể tự hỏi tại sao 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. 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, 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:

“fred” x 3 # là “fredfredfred” “barney” x (4+1) # là “barney” x 5 hay # “barneybarneybarneybarneybarney” (3+2) x 4 # là 5 x 4, hay thực sự “5” x 4, là

“5555”

Thí dụ cuối cùng đáng để xem xét chậm rãi. Các dấu

ngoặc trên (3+2) buộc cho phần này của biểu thức cần

phải được tính trước, cho năm. (Các dấu ngoặc ở đây

làm việc giống như trong C, hay trong toán học chuẩn.)

Nhưng toán tử lặp lại xâu cần một xâu cho toán hạng bên

trái, cho nên số 5 được chuyển thành xâu “5” (dùng các

qui tắc sẽ được mô tả chi tiết về sau), thành xâu một kí

tự. Xâu mới này rồi được sao lên bốn lần, cho xâu bốn kí

tự 5555. Chú ý rằng nếu đảo ngược trật tự các toán hạng,

Page 75: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

63

thì sẽ làm năm bản sao của xâu 4, cho 44444. Điều này

chỉ ra rằng việc lặp lại xâu là không giao hoá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, sẽ thực hiện phép cộng trước hay phép nhân

trước? Nếu làm phép cộng trước thì sẽ được 5*4, hay 20.

Nhưng nếu làm phép nhân trước (như vẫn được dạy

trong giờ toán) thì đượ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 điều này, nói nhân có số ưu tiên cao hơn

cộng.

Bạn có thể phá rào trật tự ưu tiên bằng việc dùng

dấu ngoặc. Bất kì cái gì trong dấu ngoặc đã được tính hết

trước khi toán tử bên ngoài dấu ngoặc được áp dụng (hệt

như bạn đã học trong giờ toán). 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 ư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

Page 76: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

64

đươ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à thực ra, thậm chí

không thể xuất hiện ở bất kì đâu trong cuốn sách này,

nhưng chớ có làm điều đó làm bạn hoảng sợ về việc đọc

chúng.) 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 có thể chẳng bao giờ 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ử

không toán tử “danh sách”

trái , (phẩy)

phải += và các toán tử khác (toán tử “gán”)

phải ? : (toán tử if/then/else ba ngôi)

không .. (toán tử phạm vi, cấu tử danh sách)

trái || (hoặc logic)

trái && (và logic)

trái | ^ (hoặc bit, hoặc bit loại trừ)

trái & (và bit)

không == != <=> eq ne cmp (toán tử “bằng”)

Page 77: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

65

Luật

kết hợp

Toán tử

không < <= > >= lt le gt ge (toán tử “không

bằng”)

không Toán tử một ngôi có tên

không -r và (các toán tử kiểm tra tệp)* khác

trái << >> (dịch chuyển bit)

trái + - . (cộng, trừ, ghép xâu)

trái * / % x (nhân, chia, lấy dư, lặp xâu)

trái =~ !~ (sánh, không sánh)

phải ** (luỹ thừa)

phải ! ~ - (phủ định logic, phủ định bit, phủ

định số)

không ++ -- (tự tăng, tự giảm)

Trong sơ đồ này, bất kì toán tử đã cho nào đã 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ó. (Điều

này ngược lại điều có lẽ bạn đang trông đợi, nhưng nó

hệt như trong sách con lạc đà, và chúng tôi cũng đã dựng

ngược nó xuống ở đó nữa.) 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

* Perl 5.0 tổ hợp các toán tử kiểm tra tệp và toán tử một ngôi có tên

vào cùng mức số ưu tiên

Page 78: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

66

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.

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 đã

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 cảnh báo* . 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 chuyển thành

không mà không có báo trước (như xâu fred được dùng

như số).

Giố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à

* Các giá trị hệ tám và hệ mười sáu không được hỗ trợ trong chuyển

đổi tự động này. dùng hex() và oct() để diễn giải các giá trị hệ mười

sáu và tám. * Trừ phi bạn bật tuỳ chọn -w trên dòng lệnh

Page 79: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

67

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) # hệt 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.

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 thực hiện chương trình.

Một biến vô hướng 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 đô la và tiếp theo sau là

một chữ, rồi thì có thể là nhiều chữ, số hay dấu gạch

thấp. 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 đã 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

Nói chung nên chọn tên biến mang 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.

Page 80: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

68

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à thực ra, khá thông

dụng. Thực ra, nó thông dụng đến mức chúng sẽ thấy

trong vài phút đây là 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 chiếu bằng dấu $ đứng trước. Trong lớp

vỏ, 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 $ riêng hoàn toàn. Nếu bạn phải viết đi viết lại các

biến rất nhiều thì bạn sẽ thấy mình ngẫu nhiên bị gõ sai.

Thường hay bị vậy. (Giải pháp của tôi là chấm dứt việc

viết chương trình vỏ, awk và C, nhưng điều đó lại có thể

không có tác dụng cho bạ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ị

Page 81: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

69

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

$d = $c = 5; # cũng điều ấy nhưng không có dấu ngoặc

Thí dụ cuối làm việc tốt vì phép gán có tính kết hợp

bên phải.

Toán tử gán hai ngôi

Các biểu thức như $a = $a + 5 (trong đó cùng một

biến lại xuất hiện ở cả hai vế của phép gán) thường xuất

hiện đến mức Perl (giống như C) có cách viết tắt cho

phép toán làm thay đổi biến - 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ị đã 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

Và tương tự như thế:

$b = $b * 3; $b *= 3;

Trong từng trường hợp, toán tử này làm cho giá trị

hiện tại của biến được thay đổi theo một cách nào đó,

Page 82: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

70

thay vì đơn giản ghi đè lên giá trị này bằng kết quả của

một biểu thức mới nào đó.

Toán tử gán thông dụng khác là toán tử ghép nối

xâu:

$str = $str . “ ”; # thêm dấucá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 đã 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ờ đã 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. Bạn chớ có làm điều này, chừng

nào bạn còn chưa vào Cuộc tranh luận Perl rối rắm.

Page 83: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

71

Tự tăng và tự giảm

Dùng cũng đã đủ dễ dàng để thêm một 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 ; # có toán tử gán ++$a; # với tự tăng tiền tố $d = 17; $e = ++$d; # $e và $d bây giờ đã 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; $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:

$x = 12; --$x ; # $x bây giờ là 11 $y = $x-- ; # $y là 11, còn $x bây giờ là 10

Page 84: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

72

Không giống C, các toán tử tự tăng và tự giảm làm

việc trên số dấu phẩy động. Cho nên việc tăng một biến

với giá trị 4.2 sẽ cho 5.2 như dự kiến.

Toán tử chop()

Một toán tử có ích 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ười”

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 cho lại 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 kí 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ả, và chẳng cho lại gì, mà cũng không đưa ra lỗi hay

than vãn gì. Phần lớn các phép toán trong Perl đã có

Page 85: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

73

những điều kiện nhạy cảm - nói cách khác, bạn có thể

dùng chúng ngay sát cạnh (và vượt ra ngoài) mà thường

không có lời phàn nàn nào. Một số người biện minh rằng

đây là một trong những nhược điểm nền tảng của Perl,

trong khi số còn lại trong chúng ta thì vẫn viết ra những

chương trình tức cười mà chẳng phải lo lắng gì về phần

rỏm bên. Bạn quyết định xem mình sẽ theo vào phía nào.

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 đô la đi theo sau một chữ,

số hay dấu gạch thấp. Khi tìm thấy một tham chiếu 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 = “fred”; $b = “some text $a”; # $b bây giờ là “some text fred” $c = “no such variable $what”; # $c là “no such variable ”

Để ngăn cả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 đô la

một dấu sổ chéo ngược, mà sẽ tắt ý nghĩa đặc biệt của

dấu đô la:

$fred = ‘hi’; $barney = “a test of “.’$fred’; # hằng kí hiệu: ‘a test of

* Và cả biến mảng nữa, nhưng chúng ta vẫn còn chưa biết đến chúng

chừng nào chưa tới Chương 3, biến mảng

Page 86: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

74

$fred’ $barney2 = “a test of \$fred”; # 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ỏ. Đơn thuần bao tên của

biến đó trong một cặp dấu ngoặc nhọn. Hay 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!”; $barney = “It’s ’$fredday”; # không phải payday, mà là

“It’s wrong!” $barney = “It’s ’${fred}day”; # bây giờ, $barney là “It’s

payday!” $barney2 = “It’s $fred”; # cách khác để làm việc đó $barney3 = “It’s “ . $fred . “day”; và một cách khác

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 $fred = “fred”; $bigfred = “\Ufred”; # cùng điều đó $capfred = “\u$fred”; # $capfred là “Fred” $barney = “\LBARNEY”; # $barney bây giờ là “barney” $capbarney = “\u\LBARNEY”; #capbarney bây giờ là

“Barney” $bigbarney = “BARNEY”; $capbarney = “\u\L$bigbarney”;

thế

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

Page 87: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

75

còn chưa được dùng tới, cho nên ngay kí tự đầu tiên của

BARNEY không tuân theo \u, nó vẫn còn là chữ hoa vì \u.

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.

<STDIN> xem như một vô hướng

Tại điểm này, nếu bạn là một người chuyên nghiệp

lập trình thì 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ì đó kì lạ, 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).

Giá 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). Đây là chỗ mà anh bạn chúng ta, toán tử

chop(), tới giúp. Một dãy cái vào điển hình đưa tới một

cái gì đó tựa như thế này:

$a = <STDIN>; # nhận văn bản chop($a); # gỡ bỏ dấu dòng mới khó chịu

Page 88: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

76

Cách viết tắt thông dụng cho hai dòng này là:

chop($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 chiếu 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ử chop()

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

chiếu tới biến đó ở bên trái của dấu bằng.)

Đưa ra bằng print()

Vậy thu được mọi thứ với <STDIN>. làm sao đưa ra

mọi thứ đây? Bằng toán tử print() đấy. Toán tử tiền tố này

nhận một giá trị vô hướng bên trong các dấu ngoặc của

nó và đưa ra mà không cần bất kì trang điểm nào lên lối

ra chuẩn. Một lần nữa, chừng nào bạn còn chưa làm điều

gì kì lạ, thì lối ra này vẫn cứ là thiết bị cuối của bạn.

Chẳng hạn:

print (“Xin chào mọi người\n”); # nói chào mọi người, tiếp là dấu dòng mới

print “Xin chào mọi người\n”; # cũng cùng điều đó

Lưu ý rằng thí dụ thứ hai chỉ ra dạng của print()

không có dấu ngoặc. Thực ra, 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 mập mờ.

Chúng ta sẽ thấy rằng bạn thực sự có thể cho print

Page 89: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

77

một danh sách các giá trị, trong mục “Dùng print để đưa

ra thông thường” ở Chương 6, Cơ sở về vào/ra, nhưng

chúng ta vẫn còn chưa nói về danh sách, cho nên chúng

ta sẽ hoãn nó về sau.

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ả, và chẳng có gì dứt khoát sẽ gây định mệnh cả.

Các biến đã 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ố không khi được dùng

như một số, hay xâu rỗng chiều dài không nếu được

dùng như một xâu.

Nhiều toán tử cho lại undef khi các đối vượt ra ngoài

phạm vi và thành vô nghĩa. Nếu bạn không làm điều gì

đặc biệt thì bạn sẽ nhận được không hay xâu không mà

không có hậu quả gì lớn. Trong thực hành, điều này gần

như không gây ra vấn đề gì.

Một toán tử mà chúng ta đã thấy có cho lại undef

trong hoàn cảnh nào đó 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 cho lại undef như một giá trị. Chúng sẽ thấy trong

chương 6 cách kiểm tra điều 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.

Page 90: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

78

Bài tập

Xem Phụ lục A về lời giải.

1. Viết chương trình tính chu vi đường tròn với bán

kính 12.5. Chu vi bằng 2 lần bán kính, hay khoảng

2 lần 3.141592654.

2. Sửa chương trình từ bài tập trước để nhắc việc nhận

bán kính từ người chạy chương trình.

3. Viết một chương trình nhắc và đọc vào hai số, rồi in

ra kết quả của việc nhân hai số đó.

4. Viết một chương trình đọc một xâu và một số rồi in

ra xâu số lần được chỉ ra bởi số các dòng tách biệt.

(Hướng dẫn: dùng toán tử “x”.)

Page 91: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

79

3

Dữ liệu mảng

và danh sách

Mảng là gì?

Mảng là một danh sách có thứ tự các dữ liệu vô

hướng. Mỗi phần tử của mảng đã là một biến vô hướng

tách biệt với một giá trị vô hướng độc lập. Các giá trị

này là được sắp thứ tự - tức là chúng có một trình tự đặc

biệt từ phần tử thấp nhất đến cao nhất.

Mảng có thể có bất kì số phần tử nào. Mảng nhỏ

nhất không có phần tử nào, trong khi mảng lớn nhất thì

có thể lấp kín toàn bộ bộ nhớ có sẵn. Một lần nữa, điều

này lại được giữ hợp với triết lí của Perl về “không có

giới hạn không cần thiết nào.”

Trong chương này:

Mảng là gì?

Biểu diễn hằng kí hiệu

Biến

Toán tử

Ngữ cảnh vô hướng và mảng

<STDIN> xem như mảng

Biến

Xen lẫn mảng

Page 92: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

80

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 mì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 (“fred”, 4.5) # hai giá trị, “fred” 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 hằ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

tử 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 giá 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

Page 93: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

81

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)

Một cách dùng của hằng kí hiệu mảng là như đối

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”) ; # ba phần tử mảng hằng kí hiệu

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à dấu dòng mới. Chuyển sang

cách dùng khác cho hằng kí hiệu mảng.

Biến

Một biến mảng giữ một giá trị mảng riêng (không

hay nhiều giá trị vô hướ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 a còng @ chứ không phải là dấu đô la $. Chẳng

hạn:

@fred # biến mảng @fred @A_Very_Long_Array_Variable_Name @A_Very_Long_Array_Variable_Name_that_is_different

Lưu ý rằng biến mảng @fred là không có quan hệ gì

theo bất kì cách nào với biến vô hướng $fred. 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.

Page 94: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

82

Một biểu thức có thể tham chiếu 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 đó.

Toán tử

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.

Phép gán

Có lẽ toán tử mảng quan trọng nhất là toán tử gán

mảng, cho mảng một giá trị. Nó là dấu bằng, giống như

toán tử gán vô hướng. Perl xác định liệu phép gán có là

phép gán vô hướng hay phép gán mảng bằng việc để ý

xem liệu phép gán là cho biến vô hướng hay mảng* .

Chẳng hạn:

@fred = (1,2,3); # mảng fred nhận ba phần tử hằng kí hiệu

@barney = @fred; # bây giờ được sao sang @barney

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 cho danh sách (1) một cách tự động

* Điều này áp dụng cho “lvalue” vô hướng hay mảng cũng như các

biến đến

Page 95: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

83

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:

@fred = (“một”, “hai”); @barney = (4,5,@fred, 6, 7); @barney trở thành (4,5,”một”,”hai”,6,7) @barney = (8, @barney); # đặt 8 vào trước @barney @barney = (@barney, “cuối”); # và “cuối” là ở cuối # @barney 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 đã ở

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ử* .

Nếu một mảng hằng kí hiệu chỉ chứa các tham chiếu

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); # đặt 1 cho $a, 2 cho $b, 3 cho $c ($a, $b) = ($b, $a); # tráo đổi $a và $b ($d, @fred) = ($a, $b, $c); # đặt $a cho $d, và ($b,$c)

cho @fred ($e,@fred) = @fred; # loại bỏ phần tử thứ nhất của

@fred là $e # điều này làm cho @fred = ($c) và $e = $b

* Perl 5.0 cho phép một tham chiếu danh sách là một phần tử danh

sách, nhưng đấy vẫn không phải là danh sách như một phần tử danh

sách

Page 96: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

84

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 của dấu bằng) đã im lặng bị loại bỏ, và bất kì biến

vượt quá nào (ở vế trái của dấu bằng) đã đượ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 đã phải ở cuối, vì biến mảng là “tham lam”,

và nó tiêu thụ tất cả các giá trị còn lại. (Này, 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:

@fred = (4, 5, 6); # khởi đầu @fred

$a = @fred; # $a nhận phần tử đầu tiên của @fred

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 @fred-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:

$a = @fred; # $a nhận chiều dài của @fred ($a) = @fred; # $a nhận phần tử đầu tiên của @fred

Phép gán đầu tiên là phép gán vô hướng, và do vậy

@fred được đối xử 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 cho phần tử đầu tiên của

@fred, im lặng bỏ đi tất cả phần còn lại.

Page 97: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

85

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:

@fred = (@barney = (2,3,4)); # @fred và @barney nhận (2,3,4)

@fred = @barney = (2,3,4); # cùng điều ấy

Truy nhập phần tử

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 nhậ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 chiếu 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 đã được

đánh số bằng việc dùng số nguyên tuần tự, 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 @fred mà được truy nhập tới là $fred[0].

Chú ý rằng @ trên tên mảng trở thành $ trên tham chiếu

phần tử. Đó là vì việc tham chiếu 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ư:

@fred = (7,8,9);

* Cũng có thể thay đổi giá trị chỉ số của phần tử đầ utiên thành một

số nào đó khác (như một) bằng việc đặt giá trị cho biến $[. Tuy

nhiên, làm như vậy có ảnh hưởng toàn cục, mà có thể gây lẫn lộn

người sẽ bảo trì chương trình của bạn, và có thể làm tan vỡ chương

trình bạn nhận được từ người khác. Do vậy, chúng tôi khuyên bạn

nên coi đây là một tính năng không nên dùng.

Page 98: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

86

$b = $fred[0]; # đặt 7 vào $b (phần tử đầu tiên của @fred)

$fred[0] = 5; # bây giờ @fred = (5,8,9)

Cũng có thể truy nhập tới các phần tử khác dễ tương

tự, như trong:

$c = $fred[1]; $ đặt 8 cho $c $fred[2]++; # tăng phần tử thứ ba của @fred $fred[1] += 4; # cộng 4 vào phần tử thứ hai ($fred[0], $fred[1]) = ($fred[1], $fred[0]); # tráo đổi hai

phần tử đầu

Việc truy nhậ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ó:

@fred[0,1] # hệt như ($fred[0], $fred[1])

@fred[0,1] = @fred[1,0] # tráo đổi hai phần tử đầu

@fred[0,1,2] = @fred[1,1,1] # làm cho cả 3 phần tử giống phần tử thứ hai

@fred[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ỉ truy 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 = (“fred”,”barney”,”betty”,”wilma”)[2,3] ;

# giống như @x = (“fred”,”barney”,”betty”,”wilma”); @who = @x[2,3]

Các giá trị chỉ số trong những thí dụ này là các số

Page 99: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

87

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:

@fred = (7,8,9); $a = 2; $b = $fred[$a]; # giống $fred[2], hay giá trị 9 $c = $fred[$a-1]; # $c nhận $fred[1], hay 8 ($c) = (7,8,9) [$a-1]; # cũng điều đó nhưng dùng lát cắt

Vậy chương trình Perl có thể có việc truy 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 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.

@fred = (7,8,9); # như trong thí dụ trước @barney = (2,1,0); @backfred = @fred[@barney]; # giống như @fred[2,1,0], hay ($fred[2],$fred[1],$fred[0]), # hay (9,8,7)

Nếu bạn truy 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 cho lại mà không có lời cảnh báo.

Chẳng hạn:

@fred = (1,2,3); $barney = $fred[7]; # $barney 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:

@fred = (1,2,3);

Page 100: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

88

$fred[3] = “hi”; # @fred bây giờ là (1,2,3,”hi”) $fred[6] = “ho”; # @fred bây giờ là (1,2,3,”hi”,undef,”ho”)

Phép gán cho một phần tử mảng có chỉ số bé hơn

không là lỗi định mệnh, vì nó có thể làm phát sinh kiểu

cách lập trình rất xấu.

Bạn có thể dùng $#fred để lấy giá trị chỉ số của phần

tử cuối của @fred. (Điều này giống như tham chiếu vỏ

C). 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 @fred, 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.

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() cho lại undef nếu đối của nó là danh

sách rỗng, thay vì làm điều gì đó khác kiểu Perl như

phàn nàn hay sinh ra thông báo lỗi.

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);

Page 101: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

89

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.

Các toán tử shift() và unshift()

Các toán tử push() và pop() làm mọi điều ở 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(@fred,$a); # như @fred = ($a,@fred); unshift(@fred,$a,$b,$c); # như @fred = ($a, $b, $c,

@fred); $x = shift(@fred); # như ($x,@fred) = @fred; # với một số giá trị thực @fred = (5,6,7); unshift(@fred,2,3,4); # @fred bây giờ là (2,3,4,5,6,7) $x = shift(@fred); # $x nhận 2, $fred nhận bây giờ là

(3,4,5,6,7)

Như với pop(), shift() cho lại undef nếu biến mảng là

rỗng.

Toán tử reverse()

Toán tử reverse() đảo ngược trật tự các phần tử của

đối của nó, cho lại danh sách kết quả. Chẳng hạn:

@a = (7,8,9);

* Trong thực tế, bạn có thể bỏ @ mặc dầu tôi nghe nói rằng Perl 5.0

lại đòi hỏi nó.

Page 102: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

90

@b = reverse(@a); # đặt $b giá trị (9,8,7) $b = reverse(7,8,9); # cũng việc ấy

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); # đóth @b là đảo ngược của chính nó

Toán tử sort()

Toán tử sort() lấy đối của nó và sắp xếp chúng dường

như chúng tất cả đã là các xâu theo trật tự ASCII tăng

dần. Nó cho lại danh sách đã sắp xếp, 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,

vân vân). 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, 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.

Toán tử chop()

Toán tử chop() 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 đã 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

Page 103: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

91

sách các dòng như các phần tử mảng tách bạch, 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 day”)

Hoàn cảnh vô hướng và mảng

Như bạn có thể thấy, từng toán tử đã được thiế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ì 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ì nói rằng toán hạng đó là được tính trong hoàn

cảnh mảng.

Thông thườ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, @fred cho lại nội dung của mảng @fred 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ự tinh vi này sẽ được

nhắc tới khi các toán tử đó được mô tả.

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 không vào cho nó*. Chẳng hạn:

* Bạn cũng có thể dùng toán tử scalar() nhưng chúng ta sẽ không

nói về điều đó ở đây.

Page 104: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

92

@a = (“x”,”y”,”z”); print (“Tôi thấy ”, @a, “ phần tử\n”); # sai, sẽ in là “xyz”

cho @a print (“Tôi thấy ”,””.@a, “ phần tưt\n”); # đúng, in 3 cho

@a

Tại đây, chúng đã nối xâu không “” vào @a, làm

nảy sinh xâu “3”, mà rồi trở thành một phần của danh

sách cho print.

Một giá trị vô hướng được dùng bên trong một hoàn

cảnh mảng thì sẽ được coi như mảng một phần tử.

<STDIN> như một mảng

Một toán tử đã thấy trước đây cũng cho giá trị khác

trong hoàn cảnh mảng là <STDIN>. Như đã mô tả trước

đây, <STDIN> cho dòng tiếp của cái vào trong hoàn cảnh

vô hướng. Bây giờ, trong hoàn cảnh mảng, toán tử này

cho lại tất cả phần dòng còn lại cho tới cuối tệp. Mỗi

dòng đã được cho lại như một phần tử tách bạch của

danh sách. Chẳng hạn:

@a = <STDIN>; # đọc cái vào chuẩn trong hoàn cảnh mảng

Nếu một người chạy chương trình này gõ vào ba

dòng, rồi nhấn Control-D (để chỉ ra “cuối tệp”), thì mảng

kết thúc với ba phần tử. Mỗi phần tử sẽ là một xâu mà

kết thúc bằng một dấu dòng mới, tương ứng với ba dòng

có kết thúc là dấu dòng mới đã gõ vào.

Xen lẫn biến mảng

Giống như các vô hướng, các giá trị mảng có thể

Page 105: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

93

được để xen lẫn trong xâu có nháy kép. Một phần tử

riêng của một mảng sẽ được thay thế bởi giá trị của nó,

giống như:

@fred = (“hello”, “dolly”); $y = 2; $x = “This is $fred[1]’s place”; # “This is dolly’s place” $x = “This is $fred[$y-1]’s place”; # cũng câu ấy

Chú ý rằng biểu thức chỉ số được tính như một biểu

thức thông thường, dường như nó ở bên ngoài xâu. Nó

không phải là biến được xen lẫn trước hết. Nói cách

khác, nếu $y chứa xâu 2*4 thì vẫn nói về phần tử 1, chứ

không phải là 7, vì 2*4 xem như một số (giá trị của $y

được dùng trong biểu thức số) chỉ là 2 rõ.

Nếu bạn muốn đặt sau một tham chiếu biến vô

hướng đến dấu ngoặc vuông trái thì bạn cần định biên

cho dấu ngoặc vuông để cho nó không được coi như một

phần của một tham chiếu mảng, như sau:

@fred = (“hello”, “dolly”); # đặt giá trị cho @fred để kiểm thử

$fred = “right”; # chúng đang định nói “this is right[1]”... $x = “this is $fred[1]”; # sai, cho “this is dolly” $x = “this is ${fred}[1]” ; # đúng (được bảo vệ bởi dấu

ngoặc nhọn) $x = “this is $fred.”.”[1]”; # đúng (xâu khác) $x = “this is $fred\[1]”; # đúng (sổ chéo ngược che dấu

nó)

Tương tự, một danh sách các giá trị từ một biến

mảng cũng có thể được xen lẫn. Việc xen lẫn đơn giản

nhất là toàn bộ mảng, được chỉ ra bằng việc cho tên

mảng (kể cả kí tự @ đứng đầu của nó). Trong trường

hợp này, các phần tử được xen lẫn theo trình tự với một

Page 106: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

94

dấu cách giữa chúng, như trong:

@fred = (“a”, “bb”, “ccc”, 1, 2, 3); $all = “Now for @fred here!”; # $all cho “Now for a bb ccc 1 2 3 here!”

Bạn cũng có thể chọn ra một phần của mảng với lát

cắt:

@fred = (“a”, “bb”, “ccc”, 1,2,3); $all = “Now for @fred[2,3] here!”; # $all cho “Now for ccc 1 here!” $all = “Now for @fred[@fred[4,5]] here!”; # cũng thế

Một lần nữa, bạn có thể dùng bất kì cơ chế nháy kép

nào đã được mô tả trước đây nếu bạn muốn đặt sau một

tham chiếu tên mảng bằng một hằng kí hiệu dấu ngoặc

nhọn trái thay vì một biểu thức chỉ số.

Bài tập

Xem Phụ lục A về lời giải

1. Viết một chương trình đọc một danh sách các xâu và

in ra danh sách theo thứ tự đảo ngược.

2. Viết một chương trình đọc một số rồi một danh sách

các xâu (tất cả đã trên các dòng tách biệt), rồi in một

trong các dòng đó từ danh sách như được lựa chọn

bởi con số đó.

3. Viết một chương trình đọc một danh sách các xâu rồi

cọn ra và in xâu ngẫu nhiên trong danh sách đó.

Chọn một phần tử ngẫu nhiên của @somearray , đặt

srand;

Page 107: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

95

tại đầu chương trình của bạn (điều này sẽ khởi đầu

bộ sinh số ngẫu nhiên), và rồi dùng

rand (@somearray)

khi bạn cần một giá trị ngẫu nhiên giữa 0 và một số

bé hơn chiều dài của @somearray.

Page 108: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

96

Page 109: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

97

4

Cấu trúc

điều khiển

Khối câu lệnh

Khối câu lệnh là một dẫy các câu lệnh, được bao

trong cặp dấu ngoặc nhọn. Nó trông tựa như thế này:

{ câu lệnh thứ nhất; câu lệnh thứ hai; câu lệnh thứ ba; ... câu lệnh cuối; }

Perl thực hiện từng câu lệnh theo trình tự, từ đầu đến

cuối. (Về sau, tôi sẽ chỉ cho bạn cách thay đổi trình tự

thực hiện này bên trong khối, nhưng hiện tại thì thế là

đủ.)

Về mặt cú pháp, một khối các câu lệnh được chấp

Trong chương này:

Khối câu lệnh

Câu lệnh if / unless

Câu lệnh while / until

Câu lệnh for

Câu lệnh foreach

Page 110: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

98

nhận ở mọi vị trí của một câu lệnh.

Câu lệnh if/unless

Độ phức tạp tiếp theo trong các câu lệnh là câu lệnh

if. Kết cấu này trông rất giống kết cấu trong C: một biểu

thức điều khiển (được tính theo tính đúng đắn của nó),

và hai khối. Nói cách khác, nó trông tựa như thế này:

if (biểu thức nào đó) { câu lệnh 1 trong trường hợp đúng ; câu lệnh 2 trong trường hợp đúng ; câu lệnh 3 trong trường hợp đúng ; } else { câu lệnh 1 trong trường hợp sai ; câu lệnh 2 trong trường hợp sai ; câu lệnh 3 trong trường hợp sai ; }

(Nếu bạn thành thạo về C thì bạn sẽ chú ý rằng các

dấu ngoặc nhọn là cần thiết. Điều này khử bỏ nhu cầu về

qui tắc “else lòng thòng”.)

Trong khi thực hiện, Perl sẽ tính biểu thức điều

khiển. Nếu biểu thức này là đúng thì khối thứ nhất (các

câu lệnh trong trường hợp đúng trên) sẽ được thục hiện.

Nếu biểu thức là sai thì khối thứ hai (các câu lệnh trong

trường hợp sai trên) sẽ được thực hiện.

Nhưng đúng sai là như thế nào? Trong Perl, các qui

tắc có đôi chút hơi huyền ảo, nhưng chúng cho bạn kết

quả như dự kiến. Biểu thức điều khiển được tính cho một

giá trị xâu (nếu nó đã là xâu, thì chẳng có thay đổi gì,

Page 111: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

99

nhưng nếu nó là số thì nó sẽ được chuyển thành xâu* ).

Nếu xâu này hoặc là xâu rỗng (chiều dài không), hoặc là

một xâu có chứa một kí tự “0” (không), thì giá trị của

biểu thức là sai. Mọi thứ khác đã được tự động coi như là

đúng. Tại sao có cái qui tắc buồn cười này vậy? Vì điều

ấy làm cho dễ dàng nhảy theo cái rỗng* so với một xâu

khác rỗng, cũng như số không so với số khác không,

không cần phải tạo ra hai cách hiểu về các giá trị đúng và

sai. Sau đây là những thí dụ về cách hiểu đúng và sai:

0 # chuyển thành “0”, cho nên là sai 1-1 # chuyển thành 0, rồi chuyển thành “0”, cho nên là

sai 1 # chuyển thành “1”, nên là đúng “” # xâu rỗng, cho nên là sai “1” # khong phải là “” hay “0”, cho nên đúng “00” # không phải là “” hay “0”, cho nên là đúng (trường

hợp này có huyền ảo, xem mà xem) “0.000” # cũng đúng với cùng lí do và cảnh báo undef # tính thành “”, cho nên sai

Về mặt thực hành mà nói, cách hiểu các giá trị đúng

sai thì khá trực giác. Đừng để tôi làm bạn sợ.

Sau đây là một thí dụ về câu lệnh if đầy đủ:

print “Bạn bao nhiêu tuổi rồi?” $a = <STDIN>; chop($a); if ($a < 18) { print “Này, bạn chưa đủ tuổi bầu cử đâu nhé?\n”; } else { print “Đủ tuổi rồi! Hãy bình thản! Vậy đi bầu cử đi!\n”;

* Bên trong, điều này không hoàn toàn đúng. Nhưng nó hành động

giống như đây là điều nó thực hiện. * Này, rỗng là ngoại trừ cho trường hợp bệnh hoạn của một kí tự

không đấy

Page 112: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

100

$voter++; # đếm số cử tri về sau }

Bạn có thể cắt bỏ khối else, chỉ để lại phần then, như

trong:

print “Bạn bao nhiêu tuổi rồi?” $a = <STDIN>; chop($a); if ($a < 18) { print “Này, bạn chưa đủ tuổi bầu cử đâu nhé?\n”; }

Đôi khi, bạn muốn bỏ đi phần then mà chỉ có phần

else, vì sẽ tự nhiên hơn để nói “làm điều đó nếu điều này

sai,” so với “làm điều đó nếu điều phủ định của điều này

là đúng.” Perl giải quyết điều này với biến thể unless:

print “Bạn bao nhiêu tuổi rồi?” $a = <STDIN>; chop($a); unless ($a < 18) { print “Đủ tuổi rồi! Hãy bình thản! Vậy đi bầu cử đi!\n”; $voter++; }

Việc thay thế if bằng unless là có hiệu quả khi nói

“Nếu biểu thức điều khiển là không đúng thì làm...” (một

unless cũng có thể có một else, như if.)

Nếu bạn có nhiều hơn hai chọn lựa thì bạn có thể

thêm một nhánh elsif vào câu lệnh if , giống như:

if (biểu thức một nào đó) { câu lệnh 1 trong trường hợp đúng một; câu lệnh 2 trong trường hợp đúng một; câu lệnh 3 trong trường hợp đúng một; } elsif (biểu thức hai nào đó ) { câu lệnh 1 trong trường hợp đúng hai; câu lệnh 2 trong trường hợp đúng hai;

Page 113: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

101

câu lệnh 3 trong trường hợp đúng hai; } elsif (biểu thức ba nào đó ){ câu lệnh 1 trong trường hợp đúng ba; câu lệnh 2 trong trường hợp đúng ba; câu lệnh 3 trong trường hợp đúng ba; } else { câu lệnh 1 trong trường hợp sai tất cả ; câu lệnh 2 trong trường hợp sai tất cả; câu lệnh 3 trong trường hợp sai tất cả; }

Mỗi biểu thức (ở đây, biểu thức một nào đó, biểu thức

hai nào đó, và biểu thức ba nào đó) đã được tính lần lượt.

Nếu một biểu thức là đúng thì nhánh tương ứng sẽ được

thực hiện, và tất cả phần còn lại của biểu thức điều khiển

cũng các nhánh câu lệnh sẽ bị bỏ qua. Nếu tất cả các

biểu thức này đã sai thì nhánh else sẽ được thực hiện

(nếu có). Bạn có thể có nhiều nhánh elsif tuỳ ý.

Câu lệnh while/until

Không một ngôn ngữ thuật toán nào hoàn chỉnh mà

không có một dạng lặp nào đó (thực hiện lặp lại một

khối các câu lệnh). Perl có thể lặp bằng việc dùng câu

lệnh while:

while (biểu thức nào đó) { câu lệnh 1; câu lệnh 2; câu lệnh 3; }

Để thực hiện câu lệnh while này, Perl tính biểu thức

điều khiển (biểu thức nào đó trong thí dụ này). Nếu giá trị

này là đúng (bằng việc dùng ý tưởng về cái đúng của câu

lệnh if), thì thân của câu lệnh while sẽ được tính một lần.

Page 114: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

102

Điều này được lặp lại cho tới khi biểu thức điều khiển

trở thành sai, tại điểm đó Perl chuyển sang câu lệnh tiếp

sau while. Chẳng hạn:

print “Bạn bao nhiêu tuổi rồi?” $a = <STDIN>; chop($a); while ($a > 0) { print “Vào lúc này bạn mới $a tuổi.\n”; $a--; }

Đôi khi nói “làm việc đó trong khi điều này sai” lại

dễ hơn là nói “làm việc đó trong khi phủ định điều này là

đúng.” Một lần nữa, Perl lại có câu trả lời. Thay cho

while là until, cũng cho kết quả mong muốn:

until (biểu thức nào đó) { câu lệnh 1; câu lệnh 2; câu lệnh 3; }

Chú ý rằng trong cả hai dạng while và until, các câu

lệnh thân sẽ bị bỏ qua hoàn toàn nếu biểu thức điều

khiển là giá trị kết thúc ngay từ lúc bắt đầu. Chẳng hạn,

nếu người dùng đưa vào một độ tuổi bé hơn không cho

đoạn chương trình trên thì Perl sẽ bỏ qua thân chu trình.

Có thể là biểu thức điều khiển sẽ chẳng bao giờ để

cho chu trình ra được. Điều này hoàn toàn hợp pháp, và

đôi khi cũng là mong muốn nữa, và do vậy không bị coi

như lỗi. Chẳng hạn, bạn có thể muốn một chu trình cứ

lặp lại mãi chừng nào bạn còn chưa phạm phải lỗi, và rồi

có một đoạn trình giải quyết lỗi đi theo sau chu trình.

Bạn có thể dùng điều này cho một việc quái quỉ cứ thế

chạy hoài cho tới khi hệ thống sập.

Page 115: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

103

Câu lệnh for

Một kết cấu lặp khác của Perl là câu lệnh for, trông

giống như câu lệnh for của C, và làm việc thì đại thể

cũng giống thế. Sau đây là nó:

for (biểu thức khởi đầu; biểu thức kiểm tra; biểu thức tăng) {

câu lệnh 1; câu lệnh 2; câu lệnh 3; }

Gỡ ra theo dạng đã thấy trước đây, điều này trở

thành

biểu thức khởi đầu while (biểu thức kiểm tra) { câu lệnh 1; câu lệnh 2; câu lệnh 3; biểu thức tăng }

Trong cả hai trường hợp, biểu thức khởi đầu đã được

tính trước. Biểu thức này về điển hình chỉ gán giá trị ban

đầu cho một biến lặp, nhưng cũng chẳng có hạn chế nào

về việc nó có thể chứa cái gì - thực ra nó có thể rỗng

(chẳng làm gì cả). Rồi biểu thức kiểm tra sẽ được tính để

xác định đúng sai. Nếu giá trị tính được là đúng thì thân

chu trình sẽ được tính, tiếp theo đó là tính biểu thức tăng

(mà điển hình là được dùng để tăng bộ lặp). Perl tiếp đó

sẽ tính lại biểu thức kiểm tra, lặp lại khi còn cần.

Thí dụ này in ra các số từ 1 đến 10, mỗi số đã có sau

nó một dấu cách:

Page 116: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

104

for ($i = 1; $i <= 10; $i++) print “$i “; }

Ban đầu, biến $i được đặt là 1. Rồi, biến này được so

sánh với 10, mà thực sự nó đang bé hơn hay bằng. Thân

của chu trình (mỗi câu lệnh print) được thực hiện, và rồi

biểu thức tăng (biểu thức tự tăng $i++) sẽ được thực

hiện, thay đổi giá trị trong $i thành 2. Vì điều này vẫn

còn bé hơn 10 nên lặp lại tiến trình, cho tới khi lần lặp

cuối mà giá trị 10 của $i đổi thành 11. Rồi điều này

không còn bé hơn hay bằng 10 nữa, cho nên chu trình đi

ra (với $i có giá trị 11).

Câu lệnh foreach

Vẫn còn một kết cấu lặp khác là câu lệnh foreach.

Câu lệnh này rất giống như câu lệnh foreach của vỏ C: nó

nhận một danh sách các giá trị và mỗi lần lại gán chúng

cho một biến vô hướng, rồi thực hiện một khối mã cùng

với việc gán đó. Nó trông tựa như thế này:

foreach $i (@danh sách nào đó) { câu lệnh 1; câu lệnh 2; câu lệnh 3; }

Không giống lớp vỏ C, giá trị nguyên gốc của biến

vô hướng được tự động khôi phục khi chu trình đi ra -

một cách khác để nói điều này là ở chỗ biến vô hướng là

cục bộ cho chu trình.

Sau đây là một thí dụ về foreach: @a = (1,2,3,4,5); foreach $b (reverse @a) {

Page 117: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

105

print $b; }

Mẩu chương trình này in ra 54321. Chú ý rằng danh

sách được foreach sử dụng có thể là một biểu thức danh

sách bất kì, không chỉ là một biến mảng. (Đây là điển

hình cho phần lớn các kết cấu Perl có yêu cầu một danh

sách.)

Có thể bỏ tên của biến vô hướng, trong trường hợp

đó Perl giả thiết rằng bạn đã xác định dùng tên biến $_.

Bạn sẽ thấy rằng biến $_ được dùng như mặc định cho

nhiều phép toán Perl, cho nên bạn có thể coi nó như một

vùng nháp. (Tất cả các phép toán có dùng $_ theo mặc

định cũng đã có thể dùng một biến vô hướng thông

thường.) Chẳng hạn, toán tử print in ra giá trị của $_ nếu

không có giá trị nào khác được xác định, cho nên thí dụ

sau đây sẽ làm việc như thí dụ trước:

@a = (1,2,3,4,5); foreach (reverse @a) { print ; }

Xem việc dùng biến $_ làm đơn giản hơn bao nhiêu

không? (Hay ít nhất thì cũng ngắn hơn.)

Nếu danh sách mà bạn đang lặp được tạo nên từ một

tham chiếu biến mảng đến, thay vì một toán tử nào đó

mà cho lại một giá trị danh sách, thì biến vô hướng đang

được dùng cho lặp thực ra lại là tham chiếu tới từng phần

tử của mảng đó, thay vì là bản sao của các giá trị đó.

Điều này ngụ ý gì theo nghĩa thông thường? Nó có nghĩa

là nếu thay đổi biến vô hướng thì cũng thay đổi phần tử

đặc biệt trong mảng mà biến đó đang đại diện cho.

Chẳng hạn:

Page 118: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

106

@a = (3,5,7,9); foreach $one ($a) { $one *= 3; } # @a bây giờ là (9, 15,21,27)

Chú ý đến việc thay đổi $one thực ra làm thay đổi

từng phần tử của @a.

Bài tập

Xem Phụ lục A về lời giải.

1. Viết một chương trình hỏi về nhiệt độ bên ngoài, rồi

in “quá nóng” nếu nhiệt độ là trên 72 F, và “quá

lạnh” trong các trường hợp khác.

2. Sửa đổi chương trình trong bài tập trước để cho nó in

ra “quá nóng” nếu nhiệt độ là trên 75F, “quá lạnh”

nếu nhiệt độ là dưới 68F, và “vừa phải” nếu nhiệt độ

trong khoảng 68 và 75.

3. Viết một chương trình đọc một danh sách các số

(mỗi số một hàng) cho tới khi đọc tới số 999, rồi in

ra toàn bộ tất cả các số đã cộng lại với nhau. (Phải

chắc đừng có cộng cả 999 vào!) Chẳng hạn, nếu bạn

đưa vào 1,2,3 và 999 thì chương trình sẽ đáp ứng với

câu trả lời 6 (1+2+3).

4. Viết một chương trình đọc một danh sách các xâu rồi

in chúng ra thành danh sách các xâu theo thứ tự đảo

ngược (không dùng toán tử reverse cho danh sách).

(Nhớ rằng toán tử <STDIN> sẽ đọc một danh sách các

xâu trên từng dòng tách biệt khi được dùng trong

hoàn cảnh mảng.)

Page 119: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

107

5. Viết một chương trình in ra bảng các số và bình

phương của chúng từ không đến 32. Thử đưa ra một

cách mà bạn không cần phải có tất cả các số từ 0 đến

32 trong danh sách, rồi thử một cách bạn phải có các

số đó. (Để trông cho đẹp,

printf “%5g %8g\n”, $a, $b

sẽ in ra $a và $b như một số có năm cột, còn $b như

một số có tám cột.

Page 120: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

108

Page 121: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

109

5

Mảng kết hợp

Mảng kết hợp là gì?

Mảng kết hợp cũng tựa như mảng (kiểu danh sách)

đã thảo luận trước đây, trong đó nó là một tuyển tập các

dữ liệu vô hướng, với các phần tử riêng được chọn ra

bằng một giá trị chỉ số nào đó. Không giống mảng danh

sách, giá trị chỉ số của mảng kết hợp không phải là số

nguyên không âm nhỏ, mà thay vào đó là vô hướng tuỳ

ý. Những vô hướng này (còn gọi là khoá) được dùng về

sau để tìm kiếm các giá trị từ mảng này.

Các phần tử của mảng kết hợp không có thứ tự đặc

biệt. Xem chúng tựa như bàn đầy những quân bài. Nửa

trên của các con bài là khoá, còn nửa dưới là giá trị của

chúng. Mỗi lần bạn đặt một giá trị vào trong mảng kết

hợp thì một con bài mới lại được tạo ra. Về sau khi bạn

Trong chương này:

Mảng kết hợp là gì?

Biến mảng kết hợp

Biểu diễn hằng cho mảng kết hợp

Các toán tử mảng kết

hợp

Page 122: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

110

muốn sửa đổi giá trị này, bạn cho khoá, còn Perl tìm ra

đúng con bài. Cho nên, thực sự, trật tự của các con bài là

không quan trọng. Thực ra, Perl cất giữ các con bài (cặp

khoá-giá trị) theo thứ tự bên trong đặc biệt để dễ dàng

tìm ra một con bài đặc biệt, cho nên Perl không phải

duyệt qua tất cả các cặp để tìm ra đúng con bài. Bạn

không thể kiểm soát được trật tự này, cho nên đừng thử.

Biến mảng kết hợp

Tên biến mảng kết hợp là dấu phần trăm (%) theo

sau bởi một chữ, theo sau nữa là không hay nhiều chữ,

chữ số và dấu gạch thấp. Nói cách khác, phần đi sau dấu

phần trăm giống hệt cái mà chúng có cho tên biến vô

hướng và biến mảng. Và giống như chẳng có quan hệ gì

giữa $fred và @fred, biến mảng kết hợp %fred cũng

chẳng liên quan gì tới hai loại biến kia cả.

Thay vì tham chiếu tới toàn bộ mảng kết hợp, thông

dụng hơn cả là tạo ra một mảng kết hợp và truy nhập vào

nó bằng cách tham chiếu tới các phần tử của nó. Mỗi

phần tử của mảng đã là một vô hướng tách biệt, được

truy nhập tới bởi một chỉ mục vô hướng, gọi là khoá.

Các phần tử của mảng kết hợp %fred vậy được tham

chiếu đến bằng $fred{$key} với $key là bất kì biểu thức

vô hướng nào. Lại chú ý rằng việc truy nhập vào một

phần tử của mảng không bắt đầu bằng cùng chỗ ngắt như

tên của toàn bộ mảng.

Giống như với mảng danh sách, có thể tạo ra những

phần tử mới bằng việc gán cho mảng một phần tử:

$fred{“aaa”} = “bbb” ; # tạo ra khoá “aaa”, giá trị “bbb” $fred{234.5} = 456.7; # tạo ra khoá “234.5”, giá trị 456.7

Page 123: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

111

Hai câu lệnh này tạo ra hai phần tử trong mảng.

Những tham chiếu về sau tới cùng những phần tử này

(dùng cùng khoá) sẽ cho lại giá trị được cất giữ.

print $fred{“aaa”}; # in “bbb” $fred{234.5} += 3; # làm cho nó thành 459.7

Việc tham chiếu tới một phần tử không có sẵn sẽ

cho lại giá trị undef, giống như với mảng danh sách hay

biến vô hướng không xác định.

Biểu diễn hằng kí hiệu cho mảng kết hợp

Bạn có thể muốn truy nhập vào mảng kết hợp như

một toàn thể, để hoặc khởi đầu nó hay sao chép nó sang

mảng kết hợp khác. Perl không thực sự có biểu diễn

hằng kí hiệu cho mảng kết hợp, cho nên thay vì thế nó

tháo rời mảng ra như một danh sách. Mỗi cặp phần tử

trong danh sách (mà bao giờ cũng phải có một số chẵn

phần tử) đã xác định ra một khoá và giá trị tương ứng

của nó. Biểu diễn tháo rời này có thể được gán vào trong

mảng kết hợp khác, mà rồi sẽ tái tạo lại cùng mảng kết

hợp đó. Nói cách khác:

@fred_list = %fred; # @fred_list nhận (“aaa”, “bbb”, “234.5”, 456.7) %barney = @fred_list; # tạo ra %barney giống %fred %barney = %fred; # cách nhanh hơn để làm cùng việc

đó %smooth = (“aaa”, “bbb”, “234.5”, 456.7); # tạo ra %smooth giống như %fred, từ các giá trị hằng kí

hiệu

Trật tự của các cặp khoá-giá trị là tuỳ ý trong cách

biểu diễn tháo rời này, và không thể kiểm soát được. Cho

dù bạn có tráo đổi một số giá trị và tạo ra một mảng như

Page 124: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

112

một toàn thể thì danh sách tháo rời thu được vẫn cứ theo

bất kì trật tự nào mà Perl đã tạo ra để truy nhập hiệu quả

vào các phần tử riêng. Bạn đừng bao giờ nên dựa trên bất

kì trật tự đặc biệt nào.

Các toán tử mảng kết hợp

Sau đây là một số toán tử cho mảng kết hợp.

Toán tử keys()

Toán tử keys(%tên mảng) cho lại danh sách các tất

cả các khoá hiện có trong mảng kết hợp %tên mảng . Nói

cách khác, nó tựa như các phần tử được đánh số lẻ (một,

ba năm vân vân) của danh sách được việc tháo rời %tên

mảng cho lại trong ngữ cảnh mảng, và thực ra, cho lại

chúng theo trật tự đó. Nếu không có phần tử nào trong

mảng kết hợp thì keys() cho lại một danh sách rỗng.

Chẳng hạn, bằng việc dùng mảng kết hợp từ thí dụ

trước:

$fred{“aaa”} = “bbb”; $fred{234.5} = 456.7; @list = keys(%fred) ; # @list nhận được (“aaa”, 234.5)

hay (234.5, “aaa”)

Các dấu ngoặc tròn là tuỳ chọn: keys %fred cũng

giống như keys(%fred).

foreach $key (key %fred) {# một lần cho mỗi khoá của %fred

print “tại $key chúng có $fred{$key}\n”; # in khoá và giá trị }

Page 125: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

113

Thí dụ này cũng chỉ ra rằng các phần tử mảng kết

hợp có thể chen lẫn nhau trong các xâu nháy kép. Tuy

nhiên bạn không thể xen lẫn toàn bộ mảng* .

Trong ngữ cảnh vô hướng, toán tử keys() cho lại số

các phần tử (cặp khoá-giá trị) trong mảng kết hợp.

Chẳng hạn, bạn có thể tìm ra liệu mảng kết hợp có rỗng

hay không:

if (keys(%mảng nào đó)) { # nếu keys() khác không: ...; # mảng là khác rỗng }

# ... hoặc ...

while (keys(%mảng nào đó) < 10) { ... ; # cứ lặp chu trình khi có ít hơn 10 phần tử }

Toán tử values()

Toán tử values(%tên mảng) cho lại một danh sách

tất cả các giá trị hiện tại của %tên mảng, theo cùng trật

tự như các khoá được toán tử keys(%tên mảng) cho lại.

Như với keys(), các dấu ngoặc tròn là tuỳ chọn. Chẳng

hạn:

%lastname = (); # buộc %lastname là rỗng $lastname(“fred”} = “flintstore”; $lastname{“barney”} = “rubble”; @lastname = values(%lastname); # lấy các giá trị

Tại điểm này @lastname chứa hoặc (“flintstore”,

“rubble”) hay đảo ngược của nó.

* Này, bạn có thể đấy, bằng cách dùng lát cắt, nhưng chúng ta không

nói về lát cắt ở đây.

Page 126: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

114

Toán tử each()

Nếu bạn muốn lặp trên (tức là xem xét mọi phần tử

của) toàn bộ mảng kết hợp, bạn có thể dùng keys(), duyệt

xét từng khoá được cho lại và nhận giá trị tương ứng.

Trong khi phương pháp này thường hay được dùng, một

cách hiệu quả hơn là dùng each(%tên mảng), toán tử sẽ

cho lại cặp khoá-giá trị như một danh sách hai phần tử.

Với mỗi lần thực hiện toán tử này cho cùng mảng, cặp

khoá-giá trị kế tiếp sẽ được cho lại, cho tới khi tất cả các

phần tử đã đã được truy nhập tới. Khi không còn cặp nào

nữa thì each() cho lạimột danh sách rỗng.

Vậy chẳng hạn, để đi qua mảng %lastname trong thí

dụ trước, làm điều gì đó tựa như thế này:

while (($first, $last) = each(%lastname)) { print “Tên cuối cùng của $first là $last\n”; }

Việc gán một giá trị mới cho toàn bộ mảng sẽ đặt lại

từng toán tử each() cho từ đầu. Việc bổ sung hay loại bỏ

các phần tử của mảng rất có thể gây ra lẫn lộn each() (và

có thể gây lẫn lộn cho cả bạn nữa).

Toán tử delete

Cho đến giờ, với điều bạn biết được, bạn có thể

thêm phần tử vào mảng kết hợp, nhưng bạn không thể

loại bỏ chúng (một việc khác hơn là gán giá trị mới cho

toàn bộ mảng). Perl cung cấp toán tử delete để loại bỏ

các phần tử. Toán hạng của delete là một tham chiếu

mảng kết hợp, hệt như nếu bạn chỉ nhìn vào một giá trị

Page 127: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

115

đặc biệt. Perl loại bỏ cặp khoá-giá trị khỏi mảng kết hợp,

và cho lại giá trị của phần tử bị xoá. Chẳng hạn:

%fred = (“aaa”, “bbb”, 234.5, 34.56) ; # cho %fred 2 phần tử

delete $fred{“aaa”}; # %fred bây giờ chỉ còn một cặp khoá-giá trị

Bài tập

Xem phụ lục A về lời giải.

1. Viết một chương trình đọc và in một xâu và giá trị

ánh xạ của nó tương ứng với ánh xạ được trình bầy

trong bảng sau:

Cái vào Cái ra

đỏ

lục

xanh

táo

đại dương

2. Viết một chương trình đọc một chuỗi các từ với một

từ trên dòng cho tới cuối tệp, rồi in ra một tóm tắt có

bao nhiêu lần mỗi từ đã gặp. (Thêm một thách thức,

sắp xếp các từ theo thứ tự giảm dần mã ASCII khi

đưa ra.)

Page 128: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

116

Page 129: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

117

6

Vào / ra cơ bản

Vào từ STDIN

Việc đọc từ lối vào chuẩn (qua bộ điều khiển Perl

được gọi là STDIN) thì thật dễ dàng. Chúng đã làm việc

này với toán tử <STDIN>. Việc tính toán tử này trong ngữ

cảnh vô hướng cho bạn một dòng tiếp của cái vào, hay

undef nếu không còn dòng nào nữa, giống như:

$a = <STDIN>; # đọc dòng tiếp

Việc tính toán tử này trong ngữ cảnh mảng sẽ cho

bạn tất cả các dòng còn lại như một danh sách - mỗi

phần tử của danh sách này là một dòng, bao gồm các kết

thúc dòng mới của nó. Chúng đã thấy điều này trước

đây, nhưng xem như việc làm mới lại, nó có thể trông

như một cái gì đó tựa như thế này:

@a = <STDIN>;

Trong chương này:

Vào từ STDIN

Vào từ toán tử Diamond

Đưa ra STDOUT

Page 130: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

118

Một cách điển hình, một trong những điều bạn muốn

làm là đọc tất cả các dòng một lúc, và làm điều gì đó trên

mỗi dòng. Một cách chung để làm điều này là:

while ($_ = <STDIN>) { # xử lí $_ tại đây (cho từng dòng) }

Cứ mỗi khi một dòng được đọc vào, <STDIN> lại cho

một giá trị đúng* , cho nên chu trình tiếp tục thực hiện.

Khi <STDIN> không còn dòng nào để đọc nữa thì nó cho

lại undef , cho giá trị sai, kết thúc chu trình.

Việc đọc một giá trị vô hướng cho <STDIN> và dùng

giá trị đó làm biểu thức điều khiển cho chu trình (như

trong thí dụ trước) thường hay xuất hiện đến mức Perl có

hẳn một cách viết tắt cho nó. Bất kì khi nào việc kiểm

thử chu trình chỉ bao gồm một toán tử đưa vào (cái gì đó

tựa như <...>), Perl tự động sao dòng được đọc vào trong

biến $_.

while ($_ = <STDIN>) { # giống “while ($_ = <STDIN>)” chop; # giống “chop($_)” # các phép toán khác với $_ ở đây }

Vì biến $_ là mặc định cho nhiều phép toán nên bạn

có thể tiết kiệm khá nhiều về việc gõ theo cách này.

* Nếu dòng cuối cùng của tệp chỉ có một kí tự “0” thì <STDIN> cho

lại undef tại dòng đó; thay vì tại cuối tệp. Nếu bạn tạo ra một tệp

giống như thế thì người lập trình Perl trên toàn thế giới sẽ gửi cho

bạn thư giận dỗi. Nhưng đó là một tệp bệnh hoạn làm phá vỡ việc

dùng loại chu trình này.

Page 131: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

119

Đưa vào từ toán tử hình thoi

Một cách khác để đọc cái vào là dùng toán tử hình

thoi: <>. Toán tử này giống như <STDIN> ở chỗ nó cho

lại một dòng riêng lẻ trong ngữ cảnh vô hướng (với undef

nếu tất cả các dòng này đã được đọc), hay tất cả các

dòng còn lại nếu được dùng trong ngữ cảnh mảng. Tuy

nhiên, khác với <STDIN>, toán tử hình thoi lấy dữ liệu

từ tệp hay các tệp được xác định trên dòng lệnh được gọi

trong chương trình Perl. Chẳng hạn, nếu bạn có một

chương trình mang tên kitty, bao gồm:

#! /usr/bin/perl while (<>) { print $_; }

và nếu bạn gọi kitty với:

kitty file1 file2 file3

thì toán tử hình thoi sẽ đọc từng dòng của file1 theo sau

bởi từng dòng của file2 và file3 lần lượt, cho lại undef khi

khi tất cả các dòng đã được đọc hết. Như bạn có thể

thấy, kitty làm việc giống như cat, gửi tất cả các dòng

của tệp có tên ra lối ra chuẩn theo tuần tự. Nếu, giống

cat, bạn không xác định bất kì tên tệp nào trên dòng lệnh

thì toán tử hình thoi sẽ tự động đọc từ lối vào chuẩn.

Về mặt kĩ thuật, toán tử hình thoi không nhìn y

nguyên vào các đối dòng lệnh - nó làm việc từ mảng

@ARGV. Mảng này là một mảng đặc biệt được bộ thông

dịch Perl đặt sẵn là một danh sách các đối dòng lệnh.

Mỗi đối dòng lệnh lại bị bỏ đi sau khi Perl đã lấy các

chuyển mạch dòng lệnh của nó để đưa vào một phần tử

tách biệt của mảng @ARGV. Bạn có thể hiểu danh sách

Page 132: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

120

này theo bất kì cách nào bạn muốn*. Bạn thậm chí có thể

đặt mảng này bên trong chương trình của mình, và có

toán tử hình thoi làm việc trên danh sách mới thay vì các

đối dòng lệnh, như thế này:

@ARGV = (“aaa”, “bbb”, “ccc”); while (<>) { # xử lí tệp aaa, bbb và ccc print “Dòng này là: $_”; }

Trong Chương 10, Giải quyết tệp và kiểm thử tệp,

chúng ta sẽ thấy cách mở và đóng các tệp xác định vào

thời điểm xác định, nhưng kĩ thuật này đã được dùng cho

một số chương trình nhanh-và-bẩn của tôi.

Đưa ra STDOUT

Perl dùng các toán tử print và printf để ghi lên lối ra

chuẩn. Xem cách chúng được dùng.

Dùng print cho đưa ra thông thường

Chúng ta đã dùng print để hiển thị văn bản lên lối ra

chuẩn. Mở rộng thêm một chút.

Toán tử print nhận một danh sách các xâu, và gửi lần

lượt từng xâu ra lối ra chuẩn, không can thiệp hay thêm

các kí tự vào đuôi. Điều có thể không hiển nhiên là ở chỗ

print thực sự chỉ là toán tử danh sách, và cho lại một giá

trị giống như bất kì toán tử danh sách nào khác. Nói cách

* Thư viện chuẩn của Perl chứa các trình cho việc phân tích kiểu như

getop cho các đối dòng lệnh của chương trình Perl. Xam Sách con

lừa để biết thêm thông tin về thư việc này.

Page 133: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

121

khác:

$a = print (“xin chào”, “mọi người”, “\n”) ;

sẽ là một cách khác để nói xin chào mọi người. Giá trị cho

lại của print là một giá trị đúng hay sai, chỉ ra sự thành

công của việc in. Nó gần như bao giờ cũng thành công,

trừ phi bạn gặp lỗi vào/ra nào đó, cho nên $a trong

trường hợp này sẽ gần như bao giờ cũng là 1.

Đôi khi, bạn sẽ cần bổ sung thêm các dấu ngoặc vào

print như được nêu trong thí dụ này, đặc biệt nếu điều

đầu tiên bạn muốn in bắt đầu với một dấu mở ngoặc

tròn, như trong:

print (2+3), “xin chào”; # sai! in 5, bỏ qua “xin chào’ print ((2+3), “xin chào”); # đúng! in 5xin chào print 2+3, “xin chào”; # cũng đúng! in 5xin chào

Dùng printf cho cái ra có dạng thức

Bạn có thể muốn có một chút ít kiểm soát với cái ra

hơn là khả năng print cung cấp. Thực ra, bạn có thể quen

với cái ra có dạng thức của hàm printf của C. Chớ có sợ -

Perl cung cấp một phép toán tương ứng với cùng tên.

Toán tử printf nhận một danh sách đối (được bao

trong dấu ngoặc tròn tuỳ chọn, như toán tử print). Đối thứ

nhất là một xâu kiểm soát dạng thức, xác định cách in

các đối còn lại. Nếu bạn còn chưa quen thuộc với hàm

printf chuẩn, thì bạn nên kiểm tra xem lại hàm printf. Tuy

nhiên, xem như một thí dụ:

printf “%15s %5d %10.2f\n”, $s, $n, $r;

sẽ in ra $s trong một trường 15 kí tự, rồi đến dấu

Page 134: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

122

cách, rồi đến $n xem như một số nguyên trong trường 5

kí tự, rồi đến một dấu cách khác, đến $r như giá trị dấu

phẩy động với 2 vị trí thập phân trong một trường 10 kí

tự, và cuối cùng là một dấu dòng mới.

Bài tập

Xem Phụ lục A về lời giải.

1. Viết một chương trình hành động như cat, nhưng đảo

ngược thứ tự các dòng. (Một số hệ thống có tiện ích

kiểu như vậy mang tên tac.)

2. Viết một chương trình đọc một danh sách các xâu rồi

in ra xâu trong một cột có căn lề phải 20 kí tự. Chẳng

hạn, đưa vào xin chào, tạm biệt in ra xin chào và tạm

biệt được căn lề phải trong cột 20 kí tự.

3. Sửa đổi chương trình của bài tập trước để cho phép

người dùng chọn lấy chiều rộng cột. Chẳng hạn, đưa

vào 20, xin chào và tạm biệt cũng làm cùng việc như

chương trình trước đã làm, nhưng đưa vào 30, xin

chào và tạm biệt thì phải căn lề xin chào và tạm biệt

theo cột 30 kí tự.

Page 135: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

123

7

Biểu thức

chính qui

Khái niệm về biểu thức chính qui

Biểu thức chính qui là một khuôn mẫu - một khuôn

mẫu - để được sánh với một xâu. Việc sánh một biểu

thức chính qui với một xâu thì hoặc thành công hoặc thất

bại. Đôi khi, sự thành công hay thất bại này có thể là tất

cả những gì bạn quan tâm tới. Vào lúc khác, bạn sẽ

muốn lấy một khuôn mẫu đã sánh đúng và thay thế nó

bằng một xâu khác, một phần trong đó có thể phụ thuộc

đích xác vào cách thức và nơi chốn mà biểu thức chính

qui được sánh đúng.

Biểu thức chính qui thường được nhiều chương trình

UNIX dùng tới, như grep, sed, awk, ed, vi, emacs và

Trong chương này:

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 qui

Khuôn mẫu

Thêm về toán tử đối sánh

Phép thế

Toán tử split() và join()

Page 136: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

124

thậm chí cả nhiều vỏ nữa. mỗi chương trình đã có một

tập các kí tự khuôn mẫu khác nhau (phần lớn là chờm

lên nhau). Perl là một siêu tệp ngữ nghĩa cho tất cả

những công cụ này - bất kì biểu thức chính qui nào mà

có thể được viết trong một trong những công cụ UNIX

này thì cũng đã có thể được viết trong Perl, nhưng không

nhất thiết dùng hệt các kí tự đó.

Cách dùng đơn giản về biểu thức chính qui

Nếu chúng tìm tất cả các dòng của một tệp có chứa

xâu abc, thì có thể dùng chỉ lệnh grep:

grep abc sonefile > result

Trong trường hợp này, abc là biểu thức chính qui mà

chỉ lệnh grep lấy để kiểm tra cho từng dòng đưa vào.

Những dòng sánh đúng sẽ được chuyển ra lối ra chuẩn (ở

đây, kết thúc với tệp result vì việc chuyển hướng của vỏ).

Trong Perl, có thể nói về xâu abc như biểu thức

chính qui bằng việc bao xâu này trong hai dấu sổ chéo:

if (/abc/) { print “$_”; }

Nhưng cái gì được kiểm tra so với biểu thức chính

qui abc trong trường hợp này? Tại sao, anh bạn cũ của

chúng ta, biến $_ lại có mặt ở đây? Khi một biểu thức

chính qui được bao trong hai dấu sổ chéo (như trên), thì

biến $_ sẽ được kiểm tra theo biểu thức chính qui đó.

Nếu biểu thức chính qui sánh đúng, thì toán tử sánh sẽ

cho lại giá trị đúng. Ngoài ra, nó cho lại giá trị sai.

Trong thí dụ này, biến $_ được giả sử có chứa một

Page 137: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

125

dòng văn bản nào đó, và được in ra nếu dòng này có

chứa các kí tự abc đâu đó bên trong dòng - tương tự như

chỉ lệnh grep ở trên. Không giống như chỉ lệnh grep, vận

hành trên tất cả các dòng của tệp, đoạn chương trình Perl

này chỉ nhìn vào có một dòng thôi. Để làm việc trên tất

cả các dòng, cần thêm vào một chu trình, như trong:

while (<>) { if (/abc/) { print “$_”; } }

Điều gì sẽ xảy ra nêu như không biết được số của

các b giữa a và c? Tức là, điều gì sẽ xảy ra nếu muốn in

dòng có chứa một a và theo sau nó là không hay nhiều b,

rồi theo sau nữa là một c? Với grep, phải nói:

grep “ab*c” somefile > result

(Đối này có chứa dấu sao trong ngoặc kép bởi vì

chúng không muốn lớp vỏ trải rộng đối đó cứ như là một

mẫu tên tệp. Nó phải được truyền qua grep để có hiệu

quả.) Thế mà trong Perl, chúng ta có thể nói đích xác

cùng điều đó:

while (<>) { if (/ab*c/) { print “$_”; } }

Cũng hệt như grep, điều này có nghĩa là một a theo

sau bởi không hay nhiều b, theo sau là c.

Chúng ta sẽ xem xét nhiều tuỳ chọn khác về toán tử

đối sánh trong mục “Nói thêm về toán tử đối sánh”, ở

cuối chương này, sau khi đã nói về tất cả các loại biểu

Page 138: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

126

thức chính qui.

Một toán tử biểu thức chính qui nữa là toán tử thay

thế, làm việc thay thế một phần của xâu mà sánh đúng

biểu thức chính qui bằng một xâu khác. Toán tử thay thế

giống như chỉ lệnh s trong sed, bao gồm một chữ s, một

sổ chéo, một biểu thức chính qui, một sổ chéo, một xâu

thay thế, và một sổ chéo cuối cùng, trông tựa như thế

này:

s/ab*c/def/;

Xâu (trong trường hợp này là biến $_) được đem ra

đối sánh với biểu thức chính qui (ab*c). Nếu việc đối

sánh thành công, thì phần của xâu sánh đúng sẽ bị loại ra

và được thay thế bằng xâu thay thế (def). Nếu việc đối

sánh không thành công thì chẳng có gì xảy ra cả.

Như với toán tử đối sánh, sẽ còn xem xét lại vô số

các tuỳ chọn về toán tử thay thế dưới đây, trong mục

“Thay thế”.

Khuôn mẫu

Một biểu thức chính qui là một khuôn mẫu. Một số

phần của khuôn mẫu sánh đúng chỉ các kí tự trong xâu

thuộc kiểu đặc biệt. Những phần khác của khuôn mẫu

sánh đúng cho đa kí tự, hay đa đa kí tự. Trước hết, sẽ

xem các khuôn mẫu một kí tự, rồi đến các khuôn mẫu đa

kí tự.

Page 139: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

127

Khuôn mẫu một kí tự

Kí tự sánh mẫu đơn giản nhất và thông dụng nhất

trong các biểu thức chính qui là một kí tự sánh với chính

nó. Nói cách khác, đặt một chữ a vào trong biểu thức

chính qui đòi hỏi một chữ tương ứng a trong xâu.

Kí tự sánh mẫu thông dụng nhất tiếp đó là dấu chấm

“.”. Dấu chấm đối sánh bất kì kí tự riêng lẻ nào ngoại trừ

dấu dòng mới (\n). Chẳng hạn, khuôn mẫu /a./ đối sánh

bất kì dãy hai kí tự nào bắt đầu bằng a và không phải là

“a\n”.

Lớp kí tự sánh mẫu được biểu diễn bởi cặp dấu

ngoặc vuông mở và đóng, và một danh sách các kí tự

nằm giữa hai dấu ngoặc này. Một và chỉ một trong các kí

tự này phải hiện diện tại phần tương ứng của xâu cần

sánh mẫu. Chẳng hạn,

/[abcde]/

sánh với bất kì một trong năm chữ đầu tiên của bảng

chữ thường, trong khi

/[aeiouAEIOU]/

lại sánh với bất kì năm nguyên âm hoặc chữ thường

hoặc chữ hoa. Nếu bạn muốn đặt dấu ngoặc vuông phải

(]) vào danh sách thì đặt một sổ chéo ngược ở trước nó,

hay đặt nó như kí tự đầu tiên bên trong danh sách. Phạm

vi của các kí tự (như a tới z có thể được viết tắt bằng

việc chỉ ra những điểm cuối của phạm vi được tách biệt

bởi dấu gạch ngang (-); để có được hằng kí hiệu gạch

ngang, đặt trước dấu gạch ngang một sổ chéo ngược. Sau

đây là một số thí dụ khác:

[0123456789] # sánh với mọi chữ số

Page 140: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

128

[0-9] # cũng thế [0-9\-] # sánh 0-9 hay dấu trừ [a-z0-9] # sánh bất kì chữ thường hay số nào [a-zA-Z0-9_] # sánh bất kì chữ, số hay dấu gạch thấp

Cũng có lớp kí tự bị phủ định, cũng là cùng lớp kí

tự, nhưng có thêm dấu mũi tên ngược (hay dấu mũ ^)

đằng trước, đi ngay sau dấu ngoặc trái. Lớp kí tự này đối

sánh với bất kì kí tự đơn nào không trong danh sách.

Chẳng hạn:

[^0-9] # sánh với bất kì kí tự phi số nào [^aeiouyAEIOUY] # sánh với bất kì kí tự nào không

nguyên âm [^\^] # sánh với một kí tự đơn trừ mũi tên ngược

Để tiện cho bạn, đã có định nghĩa sẵn một số lớp kí

tự chung, như được mô tả trong Bảng 7-1.

Bảng 7-1: Viết tắt cho lớp kí tự định sẵn

Kết cấu Lớp tương

đương

Kết cấu

phủ định

Lớp phủ

định tương

đương

\d (số) [0-9] \D (số,

không!)

[^0-9]

\w (từ) [a-zA-Z0-

9_]

\W (từ,

không!)

[^a-zA-Z0-

9_]

\s (cách) [ \r\t\n\f] \S (cách,

không!)

[^ \r\t\n\f]

Khuôn mẫu \d sánh với “số”. Khuôn mẫu \w sánh

Page 141: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

129

với “kí tự từ”, mặc dầu điều thực sự sánh đúng là bất kì

cái gì hợp lệ trong tên biến Perl. Khuôn mẫu \s sánh với

“dấu cách” (khoảng trắng), ở đây được xác định như dấu

cách, về đầu dòng (không hay dùng mấy trong UNIX),

tab, xuống dòng (dấu dòng mới của UNIX), và kéo giấy.

Các bản chữ hoa sánh đúng với cái đối lập cho những

lớp này.

Khuôn mẫu nhóm

Sức mạnh thực sự của biểu thức chính qui là khi bạn

có thể nói “một hay nhiều những thứ này” hay “cho tới

năm thứ này”. Ta nói về cách thực hiện điều này.

Dãy

Khuôn mẫu nhóm đầu tiên (và có lẽ kém hiển nhiên

nhất) là dãy. Điều này có nghĩa là abc sánh đúng với một

a theo sau là b, theo sau là c. Nó dường như đơn giản,

nhưng tôi cứ đặt tên cho nó để tôi có thể nói về nó sau

này.

Bội

Chúng đã thấy dấu sao (*) như một khuôn mẫu

nhóm. Dấu sao chỉ ra rằng “không hay nhiều” kí tự (hay

lớp kí tự) đứng ngay trước nó.

Hai khuôn mẫu nhóm khác làm việc giống thế là dấu

cộng (+), nghĩa là “một hay nhiều” kí tự đứng ngay

trước, và dấu hỏi (?), nghĩa là “không hay một” kí tự

ngay trước. Chẳng hạn, biểu thức chính qui /fo+ba?r/

sánh đúng cho một f theo sau là một hay nhiều o, theo

Page 142: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

130

sau là a, b và tuỳ chọn a, theo sau là một r.

Trong tất cả ba khuôn mẫu nhóm này, các khuôn

mẫu đã tham lam. Nếu một khuôn mẫu như vậy có cơ

hội sánh đúng giữa năm và mười kí tự thì nó sẽ lọc ra

xâu mười kí tự mỗi lúc. Chẳng hạn:

$_ = “fred xxxxxxxxxx barney”; s/x*/boom/;

bao giờ cũng thay tất cả các x liên tiếp bằng boom

(kết quả là fred boom barney), thay vì chỉ thay thế cho

một hay hai x, cho dù một tập x ngắn hơn cũng sánh

được cho cùng biểu thức chính qui

Nếu bạn cần nói “năm tới mười” x, thì bạn có thể

xoay xở bằng cách đặt năm x theo sau bởi năm x nữa đi

liền sau dấu chấm hỏi. Nhưng làm thế trông xấu, mà

cũng chẳng làm việc tốt lắm. Thay vì vậy, có một cách

dễ hơn: số bội tổng quát. Số bội tổng quát bao gồm một

cặp dấu ngoặc nhọn với một hay hai số bên trong, như

trong /x{5,10}/. Giống như ba số bội khác, kí tự đứng

ngay trước (trong trường hợp này là chữ “x”) phải được

tìm thấy bên trong số lần lặp đã chỉ ra (năm đến mười ở

đây).

Nếu bạn bỏ đi con số thứ hai, như trong /x{5,}/, thì

điều này có nghĩa là “nhiều hay hơn nữa” (năm hay

nhiều hơn trong trường hợp này), và nếu bạn bỏ nốt dấu

phẩy, như trong /x{5}/, thì điều đó có nghĩa là “đúng con

số này” (năm x). Để được 5 x hay ít hơn, bạn phải đặt số

không vào, như trong /x{0,5}/.

Vậy, biểu thức chính qui /a.{5}b/ sánh đúng cho kí

tự a được tách với kí tự b bởi bất kì năm kí tự khác kí tự

dòng mới. (Nhớ lại rằng dấu chấm sánh với bất kì kí tự

Page 143: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

131

khác dấu dòng mới, và chúng sánh với năm kí tự như thế

ở đây.) Năm kí tự này không cần phải như nhau. (Chúng

ta sẽ biết cách để buộc chúng là như nhau trong mục

tiếp.)

Có thể miễn trừ hoàn toàn bằng *, +, và ?, vì chúng

hoàn toàn tương đương với {0,}, {1,}, và {0,1}. Nhưng

dễ dàng hơn vẫn là gõ một kí tự ngắt tương đương, mà

cũng quen thuộc hơn.

Nếu có hai số bội trong một biểu thức, thì qui tắc

tham lam được tăng lên với “bên trái nhất là tham nhất”.

Chẳng hạn:

$_ = “a xxx c xxxxxxx d”;

/a.*c.*d/;

Trong trường hợp này, “.*” thứ nhất trong biểu thức

chính qui sánh với tất cả các kí tự cho tới c thứ hai, cho

dù việc sánh đúng chỉ với các kí tự cho tới c đầu tiên vẫn

cho phép toàn bộ biểu thức chính qui được sánh. Điều

này không tạo ra khác biệt gì (khuôn mẫu sẽ sánh theo cả

hai cách), nhưng sau này khi chúng có thể nhìn vào các

bộ phận của biểu thức chính qui mà được sánh, thì sẽ có

đôi chút vấn đề.

Điều gì xảy ra nếu biểu thức xâu và chính qui hơi bị

thay đổi đi, chẳng hạn như:

$_ = “a xxx ce xxxxxxx ci xxx d”;

/a.*ce.*d/;

Trong trường hợp này, nếu .* sánh với phần lớn các

kí tự có thể trước c tiếp, thì kí tự biểu thức chính qui tiếp

(e) sẽ không sánh với kí tự tiếp của xâu (i). Trong trường

hợp này, thu được việc lần ngược tự động - số bội bị

Page 144: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

132

tháo ra và thử lại, dùng lại tại chỗ nào đó phía trước

(trong trường hợp này, tại c trước, tiếp sau là (e)* . Một

biểu thức chính qui phức tạp có thể bao gồm nhiều mức

lần ngược như vậy, dẫn tới thời gian thực hiện lâu.

Dấu ngoặc tròn như bộ nhớ

Một toán tử nhóm khác là cặp mở và đóng ngoặc

tròn quanh bất kì phần khuôn mẫu nào. Điều này không

làm thay đổi liệu khuôn mẫu có sánh đúng hay không,

nhưng thay vì thế lại làm cho một phần của xâu được

khuôn mẫu sánh đúng sẽ được ghi nhớ, để cho nó có thể

được tham chiếu tới về sau. Vậy chẳng hạn, (a) vẫn sánh

với a, còn ([a-z]) thì vẫn sánh với bất kì chữ thường nào.

Để nhớ lại một phần đã ghi nhớ của một xâu, bạn

phải đưa vào một dấu sổ chéo ngược theo sau bởi một số

nguyên. Kết cấu khuôn mẫu này biểu thị cho cùng dãy

các kí tự được sánh trước đây trong cặp dấu ngoặc tròn

cùng số (đếm từ một) . Chẳng hạn:

/fred(.)barney\1/;

sánh một xâu có chứa fred, tiếp theo là một kí hiệu

khác dấu dòng mới, tiếp nữa là barney, rồi tiếp bởi cùng

một kí tự đó. Vậy, nó sánh với fredxbarneyx, nhưng

không sánh với fredxbarneyy. So sánh điều đó với:

/fred.barney./’

trong đó hai kí tự không xác định có thể là một, hay

* Về mặt kĩ thuật, có nhiều cách lần ngược của toán tử * để tìm ra c

ở vị trí đầu tiên. Nhưng phải hơi thủ thuật hơn để mô tả nó, mà nó

vẫn hoạt động theo cùng nguyên lí.

Page 145: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

133

khác nhau - cũng chẳng thành vấn đề gì.

Số 1 đến từ đâu vậy? Nó có nghĩa là phần biểu thức

chính qui nằm trong dấu ngoặc đầu tiên. Nếu có nhiều

phần như thế, thì phần thứ hai (đếm các dấu ngặc trái từ

trái sang phải) sẽ được tham chiếu tới là \2, phần thứ ba

là \3, và cứ thế. Chẳng hạn:

/a(.)b(.)c\2d\1/;

sẽ sánh với một a, một kí tự (gọi nó là #1), một b,

một kí tự khác (gọi nó là #2), một c, kí tự #2, một d, và

kí tự #1. Cho nên nó sánh với axbycydx, chẳng hạn.

Phần được tham chiếu tới có thể nhiều hơn một kí

tự. Chẳng hạn:

/a(.*)b\1c/;

sánh với một a, theo sau bởi một số bất kì kí tự nào

(thậm chí không), theo sau bởi b, theo sau bởi cùng dãy

kí tự đó, theo sau bởi c. Vậy, nó sẽ sánh với

aFREDnFREDc, hay thậm chí abc, nhưng không

aXXbXXXc.

Một cách dùng khác của phần được nhớ của biểu

thức chính qui là trong xâu thay thế của chỉ lệnh thay

thế. Kết cấu kiểu \1 vẫn giữ giá trị của chúng trong xâu

thay thế, và có thể được tham chiếu tới để xây dựng xâu,

như trong:

$_ = “a xxx b yyy c zzz d”;

s/b(.*)c/d\1e/;

mà sẽ thay thế b và c bằng d và e, vẫn giữ lại phần ở

giữa.

Page 146: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

134

Thay phiên

Một kết cấu nhóm khác là thay phiên, như trong

a|b|c. Điều này có nghĩa là sánh đúng một trong các khả

năng (a hay b hay c trong trường hợp này). Điều này vẫn

có tác dụng ngay cả khi các thay phiên có nhiều kí tự,

như trong /song|blue/, sẽ sánh hoặc song hoặc blue. (Với

những thay phiên đơn giản, tốt hơn cả là bạn có thể bỏ

lớp kí tự như /[abc]/.)

Điều gì xảy ra nếu muốn sánh songbird hay bluebird?

có thể viết /songbird|bluebird/, nhưng phần bird đó không

nên có đó hai lần. Thực ra, cũng có cách ra, nhưng phải

nói tới thứ tự ưu tiên cho các khuôn mẫu nhóm, sẽ được

đề cập tới trong mục “Thứ tự ưu tiên” dưới đây.

Khuôn mẫu neo

Bốn kí pháp đặc biệt đóng neo cho một khuôn mẫu.

Thông thường, khi một khuôn mẫu được sánh với xâu thì

sự bắt đầu của khuôn mẫu đó được rê đi trong toàn bộ

xâu từ trái sang phải, sánh với cơ hội có thể đầu tiên.

Neo cũng cho phép bạn đảm bảo rằng các phần của dòng

khuôn mẫu sắp thẳng với những phần đặc biệt của xâu.

Cặp neo thứ nhất đòi hỏi rằng một phần đặc biệt của

việc đối sánh phải được định vị tại biên giới từ hay

không tại biên giới từ. Neo \b yêu cầu một biên giới từ

tại điểm đã chỉ ra cho khuôn mẫu đối sánh. Biên giới từ

là nơi ở giữa các kí tự sánh với \w và \W, hay giữa các kí

tự sánh với \w và chỗ bắt đầu hay kết thúc của xâu. Chú

ý rằng điều này ít phải xử lí đối với tiếng Anh và phải

làm nhiều đối với các kí hiệu C, nhưng điều đó cũng gần

Page 147: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

135

thôi khi đạt tới. Chẳng hạn:

/fred\b/; # sánh fred, nhưng không Frederick /\bwiz/; # sánh wiz và wizard, nhưng không qwiz /\bFred\b/; # sánh Fred nhưng không Frederick hay

alFred /abc\bdef/; # không bao giờ sánh (không thể có cận ở

đây)

Giống thế, \B yêu cầu không có biên giới từ tại vị trí

đã chỉ ra. Chẳng hạn:

/\bFred\B/; # sánh “Frederick” nhưng không “Fred Flintstonee”

Hai neo nữa yêu cầu rằng một phần đặc biệt của

khuôn mẫu phải đi ngay sau cuối xâu. Dấu mũ (^) sánh

với điểm bắt đầu của xâu nếu nó đang ở một vị trí tạo ra

nghĩa để đối sánh tại chỗ bắt đầu của xâu. Chẳng hạn, ^a

sánh một a nếu và chỉ nếu a là kí tự đầu tiên của xâu.

Tuy nhiên, ^a cũng sánh với hai kí tự a và ^ ở bất kì đâu

trong xâu. Nói cách khác, dấu mũ đã mất ý nghĩa đặc

biệt của nó. Nếu bạn cần dấu mũ là một hằng kí hiệu dấu

mũ ngay tại chỗ bắt đầu, đặt một dấu sổ chéo ngược phía

trước nó.

Dấu $ cũng giống như ^, neo lại khuôn mẫu, nhưng

tại cuối của xâu, không phải bắt đầu. Nói cách khác,

c$ sánh với một c chỉ nếu nó xuất hiện tại cuối xâu. Dấu

đô la ở bất kì nơi đâu khác trong khuôn mẫu có lẽ vẫn cứ

được diễn giải như cách hiểu giá trị vô hướng, cho nên

bạn gần như bao giờ cũng phải dùng dấu sổ chéo ngược

để đối sánh một dấu hiệu đô là hàng kí hiệu trong xâu.

Page 148: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

136

Thứ tự ưu tiên

Vậy điều gì xảy ra khi lấy a|b* cùng nhau? Liệu đây

là a hay b một số lần bất kì hay chỉ một a hay nhiều b?

Được rồi, cũng giống như các toán tử có số ưu tiên,

các khuôn mẫu bỏ neo và gộp nhóm cũng có số ưu tiên.

Số ưu tiên của khuôn mẫu từ cao xuống thấp nhất được

cho trong Bảng 7-2.

Bảng 7-2: Số ưu tiên toán tử gộp nhóm biểu thức

chính qui (cao nhất xuống thấp nhất)

Tên Biểu diễn

Dấu ngoặc tròn ( )

Số bội + * ? {m,n}

Tuần tự và bỏ neo abc^$\b\B

Thay phiên |

Theo bảng này, * có số ưu tiên cao hơn |. Cho nên

/a|b*/ được diễn giải như một a, hay số bất kì b.

Điều gì xảy ra nếu muốn một nghĩa khác, như trong

“bất kì số a hay b nào”? Chúng đơn thuần chỉ ném vào

một cặp dấu ngoặc. Trong trường hợp này, bao phần của

biểu thức mà toán tử * cần áp dụng, vào bên trong các

dấu ngoặc, và sẽ được nó, như (a|b)*. Nếu bạn muốn làm

rõ ràng biểu thức thứ nhất, thì bạn có thể đóng dấu ngoặc

dư thừa nó với a|(b*).

Khi bạn dùng dấu ngoặc để tác động tới số ưu tiên

chúng cũng đặt lẫy bộ nhớ, như đã chỉ ra trước đây trong

chương này. Tức là tập các dấu ngoặc này sẽ cần được

đếm khi bạn muốn nói tới một cái gì đó là \2, \3 hay bất

Page 149: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

137

kì cái gì. Một ngày nào đó có thể có một loại dấu ngoặc

mà không phải đếm, nhưng chưa có trong lần đưa ra Perl

4.036.

Sau đây là một số thí dụ khác về biểu thức chính

qui, và tác động của dấu ngoặc:

abc* $ sánh với ab, abc, abcc, abccc, abcccc vân vân (abc)* # sánh với “”, abc, abcabc, abcabcabc vân vân ^x|y # sánh x tại đầu dòng, hay y ở bất kì đâu ^(x|y) # sánh hoặc với x hoặc với y tại đầu dòng a|bc|d # a hoặc bc hoặc d (a|b)(c|d) # ac, ad, bc hoặc bd (song|blue)bird # songbird hay bluebird

Thêm về toán tử đối sánh

Chúng ta đã nhìn vào cách dùng đơn giản nhất của

toán tử đối sánh (một biểu thức chính qui được bao trong

sổ chéo). Bây giờ nhìn vào vô vàn cách làm cho toán tử

này làm được điều gì đó hơi khác hơn.

Chọn một mục tiêu khác (toán tử =~)

Đôi khi xâu bạn muốn sánh với khuôn mẫu lại

không ở bên trong biến $_, và đó sẽ là sắc thái để đặt nó

ở đó (có lẽ bạn đã có một giá trị trong $_ mà bạn rất

thích). Không hề gì. Toán tử =~ sẽ giúp ở đây. Toán tử

này nhận một toán tử biểu thức chính qui ở vế bên phải,

rồi thay đổi đối tượng của toán tử này thành một cái gì

đó bên cạnh biến $_ - có nghĩa là một giá trị nào đó có

tên bên vế trái của toán tử này. Nó trông tựa như thế này:

$a = “hello world”;

Page 150: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

138

$a =~ /^he/; # đúng $a =~ /(.)\l/; # cũng đúng (sánh với hai l) if ($a =~ /(.)\1/) { # đúng, cho nên có ... # một số chất liệu khác }

Mục tiêu của toán tử =~ có thể là bất kì biểu thức

nào cho một giá trị xâu vô hướng nào đó. Chẳng hạn,

<STDIN> cho một giá trị xâu vô hướng khi được dùng

trong hoàn cảnh vô hướng, cho nên chúng có thể tổ hợp

điều này với toán tử =~ và một toán tử sánh biểu thức

chính qui để được một kiểm tra gọn gàng về cái vào đặc

biệt, như trong:

print “còn yêu cầu nào nữa không?”; if (<STDIN> =~ /^[yY]/) { # cái vào có bắt đầu bằng một y

không? print “Vậy yêu cầu đó có thể là gì? ”; <STDIN>; # bỏ một dòng cái vào chuẩn print “Rất tiếc, tôi không thể làm được điều đó.\n”;

Trong trường hợp này, toán tử <STDIN> cho dòng

tiếp từ cái vào chuẩn, mà rồi ngay lập tức được dùng như

xâu đem sánh với khuôn mẫu ^[yY]. Lưu ý rằng bạn

chưa bao giờ cất giữ cái vào vào một biến, cho nên nếu

bạn muốn sánh cái vào với mẫu khác, hay có thể cho

hiện lại dữ liệu trong một thông báo lỗi thì bạn không

gặp may rồi. Nhưng dạng này thường hay đến đúng lúc.

Bỏ qua chữ hoa thường

Trong thí dụ trước, tôi đã dùng [yY] để đối sánh

hoặc chữ Y hoa hoặc y thường. Với những xâu rất ngắn

như y hay fred thì điều này là dễ dàng, như [fF]

[oO][oO]. Nhưng điều gì xẩy ra nếu xâu tôi muốn sánh

Page 151: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

139

lại là từ “procedure” trong hoặc chữ thường hoặc chữ

hoa?

Trong một số bản của grep, cờ -i chỉ ra “bỏ qua hoa

thường”. Perl cũng có tuỳ chọn như vậy. Bạn chỉ ra tuỳ

chọn bỏ qua hoa thường bằng cách thêm vào chữ i

thường vào sau sổ chéo đóng, như trong /somepattern/i.

Điều này nói lên rằng các chữ của khuôn mẫu này sẽ

sánh với các chữ trong xâu trong cả chữ hoa lẫn thường.

Chẳng hạn, để sánh từ “procedure” trong cả hoa lẫn

thường tại đầu dòng, dùng /^procedure/i.

Bây giờ thí dụ trước của trong giống thế này:

print “còn yêu cầu nào nữa không?”; if (<STDIN> =~ /^y/i) { # cái vào có bắt đầu bằng một y

không? # có! xử lí cho nó ... }

Dùng một định biên khác

Nếu bạn đang tìm kiếm một xâu với một biểu thức

chính qui có chứa kí tự sổ chéo (/), thì bạn phải đặt trước

mỗi sổ chéo một sổ chéo ngược (\). Chẳng hạn, bạn có

thể tìm một xâu bắt đầu bằng /usr/etc tựa như thế này:

$path = <STDIN>; # đọc một tên đường dẫn (có lẽ từ “find”?)

if ($path =~ /^\/usr\/etc/) { # bắt đầu với /usr/etc ... }

Như bạn có thể thấy, tổ hợp sổ chéo ngược-sổ chéo

làm cho nó trông giống như có một thung lũng nhỏ giữa

hai mẩu văn bản. Làm điều này cho nhiều sổ chéo có thể

Page 152: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

140

gây cồng kềnh, cho nên Perl cho phép bạn xác định một

kí tự định biên khác. Chỉ cần đặt trước bất kì kí tự phi

chữ-số* nào (định biên do bạn chọn) với một m, rồi liệt

kê khuôn mẫu của bạn theo sau bởi một kí tự định biên y

hệt thế nữa, là bạn đã hoàn thành, như trong:

/^\/usr/etc/ # dùng định biên sổ chéo chuẩn m@^/usr/etc@ # dùng @ làm định biên m#^/usr/etc# # dùng # làm định biên (sở thích của tôi)

Bạn có thể thậm chí dùng cả sổ chéo lần nữa nếu

bạn cứ muốn, như trong m/fred/, cho nên toán tử sánh

biểu thức chính qui thông thường thực sự là toán tử m,

tuy nhiên, m là tuỳ chọn, nếu bạn chọn sổ chéo làm định

biên.

Dùng xen lẫn biến

Một biểu thức chính qui là được xen lẫn biến trước

khi nó được xem xét cho các kí tự đặc biệt khác. Do đó,

bạn có thể xây dựng một biểu thức chính qui từ các xâu

được tính toán thay vì chỉ là hằng kí hiệu. Chẳng hạn:

$what = “bird”; $sentence = “Mọi con chim tốt đã bay.”; if ($sentence =~ /\b$what\b/) { print “Câu này chứa từ $what!\n”; }

Tại đây chúng đã xây dựng một cách có hiệu quả

toán tử biểu thức chính qui /\bbird\b/ bằng việc dùng một

* Nếu dấu định biên ngẫu nhiên là kí tự bên trái hay cặp trái-phải

(ngoặc tròn, ngoặc nhọn hay ngoặc vuông), thì dấu định biên đóng

là kí tự bên phải của cùng cặp đó. Nhưng ngoài ra, các kí tự là hệt

nhau cho bắt đầu và kết thúc.

Page 153: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

141

tham chiếu biến.

Sau đây là một thí dụ có hơi phức tạp hơn:

$sentence = “Mọi con chim tốt đã bay.”; print “Tôi phải tìm cái gì đây?”; $what = <STDIN>; chop ($what) ; if ($sentence =~ /$what/) { # tìm thấy nó! print “Tôi đã thấy $what trong $sentence.\n”; } else { print “không... chẳng thấy cóc gì cả.\n”; }

Nếu bạn đưa vào bird, thì nó được tìm ra. Nếu bạn

đưa vào scream nó sẽ không tìm thấy. Nếu bạn đưa vào

[bw]ird, điều ấy cũng được tìm ra, chỉ ra rằng các kí tự đối

sánh khuôn mẫu biểu thức chính qui quả thực là vẫn có ý

nghĩa. Tôi sẽ chỉ ra cho bạn trong phần “Thay thế” dưới

đây về cách thay đổi xâu để cho các kí tự đối sánh khuôn

mẫu chính qui được tắt đi.

Biến chỉ đọc đặc biệt

Sau khi đối sánh khuôn mẫu thành công, các biến

$1, $2, $3 vân vân sẽ được đặt cho cùng giá trị là \1, \2,

\3 vân vân. Bạn có thể dùng điều này để nhìn vào một

phần của việc đối sánh trong đoạn chương trình sau.

Chẳng hạn:

$_ = “đây là phép kiểm tra”; /(\W+)\W+(\W+)/; # đối sánh hai từ đầu # $1 bây giờ là “đây” còn $2 bây giờ là “là”

Bạn cũng có thể thu được cùng các giá trị ($1, $2,

$3 vân vân) bằng việc đặt đối sánh trong hoàn cảnh

Page 154: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

142

mảng. Kết quả là một danh sách các giá trị mà sẽ được

đặt cho $1 cho tới số các vật được ghi nhớ, nhưng chỉ

nếu biểu thức chính qui sánh đúng. Lấy lại thí dụ trước

theo cách khác

$_ = “đây là phép kiểm tra”; ($first, $second) = /(\W+)\W+(\W+)/; # đối sánh hai từ

đầu # $first bây giờ là “đây” còn $second bây giờ là “là”

Lưu ý rằng các biến $1 và $2 vẫn không bị thay đổi.

Các biến chỉ đọc được xác định trước còn bao gồm

$&, mà là một phần của xâu sánh đúng với biểu thức

chính qui; $`, là một phần của xâu trước phần sánh đúng;

còn $’ là phần của xâu sau phần sánh đúng. Chẳng hạn:

$_ = “đây là xâu mẫu”; /xâ.*u/; # sánh “xâu” bên trong xâu # $` bây giờ là “đây là” # $& bây giờ là “xâu” # $’ bây giờ là “mẫu”

Vì tất cả những biến này đã được đặt lại cho từng

lần sánh thành công cho nên bạn nên cất giữ các giá trị

trong các biến vô hướng khác nếu bạn cần các giá trị đó

về sau trong chương trình.

Thay thế

Chúng ta đã nói về dạng đơn giản nhất của toán tử

thay thế: s/old-regex/new-string/. Bây giờ là lúc nói tới vài

biến thể của toán tử này.

Nếu bạn muốn việc thay thế vận hành trên tất cả các

đối sánh có thể thay vì chỉ việc đối sánh đầu tiên thì viết

Page 155: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

143

thêm g vào toán tử này, như trong:

$_ = “foot fool buffon”;

s/foo/bar/g; # $_ bây giờ là “bart barl bufbarn”

Xâu thay thế có biến xen vào, cho phép bạn xác định

xâu thay thế vào lúc chạy:

$_ = “hello, world” ;

$new = “goodbye”;

s/hello/$new/; # thay thế hello bằng goodbye

Các kí tự khuôn mẫu trong biểu thức chính qui cho

phép các khuôn mẫu được đối sánh, thay vì chỉ là các kí

tự cố định:

$_ = “đây là phép kiểm tra”;

s/(\w+)/<$1>/g; # $_ bây giờ là “<đây> <là> <phép> <kiểm> <tra>”

Nhớ lại rằng $1 được đặt là dữ liệu bên trong việc

đối sánh đúng mẫu trong dấu ngoặc.

Hậu tố i (hoặc trước hoặc sau g nếu có) làm cho biểu

thức chính qui trong toán tử thay thế bỏ qua chữ hoa

thường, giống như cùng tuỳ chọn trên toán tử đối sánh

đã mô tả trước đây.

Cũng vậy, giống như toán tử đối sánh, một dấu định

biên khác cũng có thể được tuyển lựa nếu sổ chéo là

không tiện. Chỉ cần dùng cùng kí tự đó ba lần:*

s#fred#barney#; # thay fred bằng barney, giống s/fred/barney/

* hoặc hai cặp sánh nhau nếu kí tự trái - phải được dùng.

Page 156: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

144

Cũng vậy, giống toán tử đối sánh, bạn có thể xác

định một mục tiêu thay phiên bằng toán tử =~. Trong

trường hợp này, mục tiêu được chọn phải là một cái gì

đó mà bạn có thể gán cho một giá trị vô hướng vào, như

một biến vô hướng hay một phần tử của mảng. Sau đây

là một thí dụ:

$which = “đây là phép thử”;

$which =~ s/thử/đố/; # $which bây giờ là “đây là phép đố”

$someplace[$here] =~ s/left/right/; # đổi một phần tử mảng

$d{”t”} =~ s/^/x/; # thay bất kì kí tự nào bằng “x “ cho phần tử mảng kết hợp

Các toán tử split() và join()

Biểu thức chính qui có thể được dùng để chặt một

xâu thành các trường. Toán tử split() thực hiện điều này

còn toán tử join() lại có thể dính các mẩu lại với nhau.

Toán tử split()

Toán tử split() nhận một biểu thức chính qui và một

xâu rồi tìm tất cả mọi sự xuất hiện của biểu thức chính

qui bên trong xâu này (dường như bạn đã thực hiện toán

tử s///g). Các bộ phận của xâu không sánh với biểu thức

chính qui sẽ được cho lại lần lượt như một danh sách các

giá trị. Chẳng hạn, sau đây là một cách phân tích các

thành tố /etc/passwd:

$line =

Page 157: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

145

“merlyn::118:10:Randal:/home/merlyn:/usr/bin/perl”; @fields = split(/:/,$line); # chặt $line ra, dùng : làm dấu

định biên # bây giờ @field là (“merlyn”, “”, “118”, “10”, “Randal”, #

“/home/merlyn”,”/usr/bin/perl”)

Lưu ý rằng trường thứ hai rỗng trở thành một xâu

rỗng. Nếu bạn không muốn điều này, đối sánh tất cả các

hai chấm trong một lần phân tách:

@fields = split(/:+/, $line);

Điều này sẽ sánh cả hai dấu hai chấm đi kèm, cho

nên sẽ không có trường thứ hai rỗng nữa.

Một xâu thông dụng để chặt biến $_, và biến thành

mặc định là:

$_ = “xâu nào đó”; @words = split(/ /); # hệt như @words = split(/ /, $_);

Lưu ý rằng đối với việc chặt này, các khoảng cách

liên tiếp trong xâu cần chặt sẽ gây ra các trường không

(xâu rỗng) trong kết quả. Một khuôn mẫu tốt hơn sẽ là /

+/, hay một cách lí tưởng /\s+/, mà sẽ đối sánh một hay

nhiều kí tự khoảng trắng. Thực ra, khuôn mẫu này là

khuôn mẫu mặc định, cho nên nếu bạn định chặt biến $_

theo các khoảng trắng, thì bạn có thể dùng tất cả các mặc

định và đơn thuần nói:

@words = split; # hệt như @words = split(/\s+/, $_);

Các trường theo sau rỗng không trở thành một phần

của danh sách. Điều này nói chung không cần quan tâm -

một giải pháp giống thế này:

$line = “merlyn::118:10:Randal:/home/merlyn:/usr/bin/perl”;

Page 158: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

146

($name, $password, $uid,$gid,$gcos,$home,$shell) = split(/:/, $line); # chặt $line ra bằng cách dùng : làm dấu

định biên

sẽ đơn thuần cho $shell một giá trị không (undef) nếu

dòng này không đủ dài, hay nếu nó chứa các giá trị rỗng

trong trường cuối. (Các trường phụ thì im lặng bị bỏ qua,

vì việc gán danh sách làm việc theo cách đó.)

Toán tử join()

Toán tử join() nhận một danh sách các giá trị và gắn

chúng lại với nhau dùng xâu gắn giữa từng phần tử danh

sách. Nó trông tựa như thế này:

$bigstring = join($glue, @list);

Chẳng hạn, để xây dựng lại dòng mật hiệu, thử một

cách kiểu như:

$outline = join(“:”, @fields);

Lưu ý rằng xâu gắn không phải là biểu thức chính

qui - chỉ là một xâu bình thường gồm không hay nhiều kí

tự.

Bài tập

Xem Phụ lục A về lời giải.

1. Xây dựng một biểu thức chính qui sánh cho:

(a) ít nhất một a theo sau bởi một số bất kì b

(b) một số bất kì dấu sổ chéo ngược theo sau bởi một

số bất kì dấu sao

Page 159: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

147

(c) ba bản sao liên tiếp của bất kì cái gì có chứa

trong $whatever

(d) bất kì năm kí tự nào, kể cả dấu dòng mới

(e) cùng một từ được viết hai hay nhiều lần trong

một hàng, với ‘từ” được xác định như dãy các kí

tự khác khoảng trắng không rỗng.

2. (a) Viết một chương trình nhận một danh sách các từ

trên STDIN và tìm một dòng có chứa tất cả năm

nguyên âm (a, e, i, o , u). Chạy chương trình này trên

/usr/dict/words và xem nó cho ra cái gì. Nói cách

khác, đưa vào:

$ myprogram < /usr/dict/words

(Điều này giả sử bạn đặt tên chương trình của mình

là myprogram)

(b) Sửa đổi chương trình này để cho năm nguyên âm

này được sắp thứ tự

3. Viết một chương trình tìm trong /etc/passwd (trên

STDIN), in ra tên đăng nhập và tên thật của từng

người dùng. (Hướng dẫn: dùng split để chặt dòng

này thành các trường, rồi s/// để bỏ các phần trường

comment đi sau dấu phẩy thứ nhất.)

4. Viết một chương trình tìm trong /etc/passwd (trên

STDIN) hai người dùng có cùng họ, rồi in ra tên của

họ. (Hướng dẫn: sau khi trích ra tên, tạo ra một mảng

kết hợp với tên đó làm khoá và số lần gặp nó là giá

trị. Khi dòng cuối của STDIN đã được đọc vào thì

duyệt qua mảng kết hợp để đếm các số lớn hơn 1.)

5. Lặp lại bài tập trước, nhưng báo cáo về tên đăng

Page 160: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

148

nhập của tất cả người dùng với cùng tên của họ.

(Hướng dẫn: thay vì cất giữ một số đếm, cất giữ một

danh sách các tên đăng nhập cách nhau bằng dấu

cách. Khi kết thúc, duyệt qua các giá trị có chứa một

dấu cách.)

Page 161: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

149

8

Hàm

Hàm hệ thống và hàm người dùng

Chúng ta đã đã thấy và đã dùng các hàm hệ thống,

như chop, print vân vân. Bây giờ nhìn vào các hàm bạn

định nghĩa ra, tạo nên các lệnh chương trình Perl.

Xác định một hàm người dùng

Một hàm người dùng, thường hay được gọi là

chương trình con hay trình con, được xác định trong

chương trình Perl của bạn bằng việc dùng một kết cấu

như:

sub subname { câu lệnh 1; câu lệnh 2; câu lệnh 3;

Trong chương này:

Các hàm hệ thống và người dùng

Định nghĩa một hàm người dùng

Cho lại giá trị

Đối

Biến cục bộ trong hàm

Page 162: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

150

}

subname là tên của chương trình con, là bất kì tên

nào giống như tên đã đặt cho biến vô hướng, mảng và

mảng kết hợp. Một lần nữa, những tên này lại đến từ một

không gian tên khác, cho nên bạn có thể có một biến vô

hướng $fred, một mảng @fred, một mảng kết hợp %fred,

và bây giờ một trình con fred.

Khối các câu lệnh đi sau tên trình con trở thành định

nghĩa của trình con. Khi trình con được gọi tới (được mô

tả ngắn gọn), thì khối các câu lệnh tạo nên trình con này

sẽ được thực hiện, và bất kì giá trị cho lại nào (được mô

tả sau đây) đã được trả về cho nơi gọi.

Chẳng hạn sau đây là một trình con cho hiển thị câu

nói nổi tiếng:

sub say_hello {

print “Xin chào, mọi người!\n”;

}

Định nghĩa trình con có thể ở bất kì đâu trong văn

bản chương trình của bạn (chúng bị bỏ qua khi thực

hiện), nhưng tôi thì thích đặt tất cả các trình con của tôi

vào cuối tệp, để cho phần còn lại của chương trình có vẻ

như là ở đầu tệp. (Nếu bạn thích nghĩ theo kiểu Pascal

thì bạn có thể đặt các trình con của mình vào đầu và các

câu lệnh thực hiện vào cuối. Điều đấy thì tuỳ bạn.)

Các định nghĩa trình con là toàn cục; không có trình

con cục bộ. Nếu bạn có hai định nghĩa trình con với cùng

tên thì trình sau sẽ đè lấp trình trước mà không có cảnh

báo gì cả.

Bên trong thân trình con, bạn có thể truy nhập hay

Page 163: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

151

đặt các giá trị cho các biến được dùng chung với phần

còn lại của chương trình (biến toàn cục). Thực ra, theo

mặc định, mọi tham chiếu biến bên trong thân trình con

đã tham chiếu tới biến toàn cục. Tôi mách bạn về các

ngoại lệ trong mục “Biến cục bộ trong hàm” ở dưới đây.

Trong thí dụ sau:

sub say_what {

print “Xin chào, $what\n”;

}

$what tham chiếu tới giá trị toàn cục cho $what mà

được dùng chung với phần còn lại của chương trình.

Gọi một hàm người dùng

Bạn gọi một trình con từ bên trong bất kì biểu thức

nào bằng việc đặt trước tên trình con này một dấu và &,

như trong:

&say_hello; # một biểu thức đơn giản $a = 3 + &say_hello; # phần của biểu thức lớn hơn for ($x = &start_value; $x < &end_value; $x +=

&increment) { ... } # gọi ba trình con để xác định các giá trị

Một trình con có thể gọi một trình con khác, và trình

con khác này đến lượt nó lại có thể gọi trình con khác

nữa, và cứ như thế, cho tới khi tất cả bộ nhớ có sẵn đã bị

chất đầy bằng địa chỉ quay về và các biểu thức được tính

toán bộ phận. (Không có tám hay 32 mức nào có thể thoả

mãn được cho người mê Perl.)

Page 164: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

152

Giá trị cho lại

Giống như trong C, một trình con bao giờ cũng là

một phần của một biểu thức nào đó (không có cái tương

đương trong lời gọi thủ tục tựa Pascal). Giá trị của việc

gọi trình con được gọi là giá trị cho lại. Giá trị cho lại

của một trình con là giá trị của biểu thức cuối cùng được

tính bên trong thân của trình con cho mỗi lần gọi.

Chẳng hạn, định nghĩa trình con này:

sub sum_of_a_and_b { $a + $b; }

Biểu thức cuối cùng được tính trong thân của trình

con này (thực ra, đó là biểu thức duy nhất được tính) là

tổng của $a và $b, cho nên tổng của $a và $b sẽ là giá trị

cho lại. Sau đây là điều đó trong hành động:

$a = 3; $b = 4; $c = &sum_of_a_and_b; #c nhận 7 $d = 3*sum_of_a_and_b; # $d nhận 21

Một trình con cũng có thể cho lại một danh sách các

giá trị khi được tính trong hoàn cảnh mảng. Xét trình con

này và lời gọi:

sub list_of_a_and_b { ($a, $b); } $a = 5; $b = 6; $c = &list_of_a_and_b; # @c nhận (5, 6)

Biểu thức cuối được tính thực sự nghĩa là biểu thức

cuối cùng được tính, thay vì là biểu thức cuối cùng được

xác định trong thân của trình con. Chẳng hạn, trình con

này cho lại $a nếu $a > 0, ngoài ra nó cho $b:

Page 165: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

153

sub gime_a_or_b { if ($a > 0) { print “chọn a ($a)\n”; $a; } else { print “chọn b ($b)\n”; $b; } }

Lưu ý rằng trình con này cũng cho hiển thị một

thông báo. Biểu thức cuối cùng được tính là $a hay $b,

mà trở thành giá trị cho lại. Nếu bạn đảo ngược các dòng

có chứa $a và print ngay trước nó, thì bạn sẽ nhận được

một giá trị cho lại là 1 (giá trị được cho lại bởi hàm print

thành công) thay vì giá trị của $a.

Tất cả chúng đã là các thí dụ khá tầm thường. Tốt

hơn cả là nên truyền các giá trị khác nhau cho mỗi lần

gọi tới một trình con thay vì phải dựa vào các biến toàn

cục. Thực ra, điều đó cũng đúng đến chỗ cần nói.

Đối

Mặc dầu các trình con có chức năng đặc biệt là có

ích, toàn bộ mức độ có ích mới trở thành sẵn có cho bạn

khi bạn có thể truyền các đối cho trình con. Trong Perl

nếu lời gọi trình con (với dấu và @ cùng tên trình con)

có theo sau nó một danh sách nằm trong ngoặc tròn, thì

danh sách này sẽ được tự động gán cho một biến đặc biệt

có tên @_ trong suốt thời gian hoạt động của trình con.

Trình con có thể truy nhập vào biến này để xác định số

các đối và giá trị của các đối đó. Chẳng hạn:

sub say_hello_to {

Page 166: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

154

print “Hello, $_[0]!\n”; # tham biến đầu là mục tiêu

}

Tại đây thấy một tham chiếu tới $_[0], chính là phần

tử đầu tiên của mảng @_. Lưu ý đặc biệt: tương tự như

dáng vẻ của chúng, giá trị $_[0] (phần tử đầu tiên của

mảng @_) chẳng có bất kì liên quan gì với biến $_ (một

biến vô hướng của riêng nó). Bạn đừng lầm lẫn chúng!

Từ chương trình này, rõ ràng nó nói hello với bất kì cái

gì chúng truyền cho nó như tham biến đầu tiên. Điều đó

có nghĩa là chúng có thể gọi nó giống thế này:

&say_hello_to (“world”); # sẽcho hello, world! $x = “somebody”; &say_hello_to ($x); # cho hello, somebody! &say_hello_to (“me”) + &say_hello_to (“you”) # và me và

you

Lưu ý rằng trong dòng cuối, giá trị cho lại không

thực sự được dùng. Nhưng trong khi tính tổng Perl phải

tính tất cả các bộ phận của nó, cho nên trình con này

được gọi hai lần.

Sau đây là một thí dụ về việc dùng nhiều hơn một

tham biến:

sub say { print “$_[0], $_[1]!\n”; }

&say (“hello”, “world”); # hello world, lần nữa &say (“goodbye”, “cruel world”) # im lặng

Các tham biến vượt quá đã bị bỏ qua - nếu bạn chưa

bao giờ nhòm ngó tới $_[3], thì Perl cũng chẳng bận

tâm. Các tham số không đủ cũng bị bỏ qua - bạn đơn

thuần nhận được undef nếu bạn nhìn vượt ra cuối của

Page 167: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

155

mảng @_, như với mọi mảng khác.

Biến @_ là cục bộ cho trình con này; nếu có một

biến toàn cục cho @_, nó sẽ được cất giữ trước khi trình

con được gọi và được khôi phục lại giá trị trước của nó

khi trở về từ chương trình con. Điều này cũng có nghĩa

là một trình con có thể truyền các đối cho một trình con

khác mà không sợ mất biến @_ riêng của nó - việc gọi

trình con lồng nhau đã nhận được @_ riêng của nó theo

cùng cách.

Xem xét lại trình “cộng a và b” của mục trước. Tại

đây một trình con thực hiện việc cộng hai giá trị bất kì,

đặc biệt, hai giá trị được truyền cho trình con này như

tham biến.

sub add_two { $_[0] + $_[1]; } print &add_two (3, 4) ; # in 7 $c = &add_two (5, 6) ; $c được 11

Bây giờ tổng quát hoá chương trình này. Nếu chúng

ta có ba, bốn hay hàng trăm giá trị cần phải cộng lại thì

sao? Chúng ta có thể làm việc đó bằng một chu trình, tựa

như:

sub add { $sum = 0 ; # khởi đầu giá trị cho sum foreach $_ (@_ ) { $sum += $_ ; # cộng từng phần tử } $sum ; # biểu thức cuối được tính: tổng của tất cả

các phần tử } $a = &add(4,5,6) ; # cộng 4+5+6 = 15, và gán cho $a print &add(1,2,3,4,5) ; # ina ra 15

Page 168: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

156

print &add(1..5); # cũng in ra 15, vì 1..5 được mở rộng

Điều gì xảy ra nếu có một biến mang tên $sum khi

gọi &add_list? Chúng ta đã đánh trúng vào nó. Trong mục

tiếp chúng ta sẽ xem cách thức tránh điều này.

Biến cục bộ trong hàm

Chúng ta đã nói tới biến @_ và cách thức việc sao

chép cục bộ được tạo ra cho từng trình con có gọi tới

tham biến. Bạn có thể tạo ra các biến vô hướng, mảng

hay mảng kết hợp của riêng mình làm việc theo cùng

cách. Bạn làm điều này với toán tử local(), nhận một danh

sách các tên biến và tạo ra các bản cục bộ của chúng

(hay các thể nghiệm, nếu bạn thích từ đao to búa lớn).

Sau đây lại là hàm cộng đó, lần này dùng local():

sub add { local ($sum) ; # làm cho $sum thành biến cục bộ $sum = 0 ; # khởi đầu giá trị cho sum foreach $_ (@_ ) { $sum += $_ ; # cộng từng phần tử } $sum ; # biểu thức cuối được tính: tổng của tất cả

các phần tử }

Khi câu lệnh thân đầu tiên được thực hiện, thì bất kì

giá trị hiện tại nào của biến toàn cục $sum cũng đã được

cất giữ và một biến mới mang tên $sum sẽ được tạo ra

(với giá trị undef). Khi trình con này đi ra, thì Perl bỏ qua

biến cục bộ và khôi phục giá trị trước (toàn cục). Điều

này vận hành cả khi biến $sum hiện là biến cục bộ của

một trình con khác (một trình con mà gọi tới trình con

này, hay một trình con gọi tới một trình mà gọi tới trình

Page 169: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

157

con này, vân vân). Các biến có thể có nhiều bản cục bộ

lồng nhau, mặc dầu bạn có thể truy nhập mỗi lúc chỉ vào

một biến.

Sau đây là cách để tạo ra một danh sách tất cả các

phần tử hơn 100 của một mảng lớn:

sub bigger_than_100 { local ($result) ; # tạm thời giữ giá trị trở về foreach $_ (@_ ) { # đi qua danh sách đối if ($_ > 100) { # có đủ tư cách không? push (@result, $_) ; # cộng nó } $result ; # cho lại danh sách cuối }

Điều gì xảy ra nếu chúng ta muốn tất cả các phần tử

này lớn hơn 50 thay vì 100? Chúng ta phải sửa chương

trình này, đổi tất cả các số 100 thành 50. Nhưng điều gì

xảy ra nếu chúng ta lại cần cả hai? Được, chúng ta có thể

thay thế 50 hay 100 bằng một biến tham chiếu. Điều này

làm chương trình trông giống thế này:

sub bigger_than { local ($n, @values) ; # tạo ra các biến cục bộ nào đó ($n, @values) = @_ ; # chặt arg thành giới hạn và giá

trị local (@result) ; # tạm thời giữ giá trị trả lại foreach $_ (@values) { # đi qua danh sách đối arg if ($_ > $n) { # có đủ tư cách không? push (@result, $_) ; # cộng nó } $result ; # cho lại danh sách cuối } # một số lời gọi @new = &bigger_than (100, @list); # @new nhận tất cả

@list > 100 @this = &bigger_than(5,1,5,15,30); # @this nhận

Page 170: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

158

(15,30)

Lưư ý rằng lần này tôi đã dùng hai biến cục bộ phụ

để đặt tên cho các đối. Điều này khá thông dụng trong

thực hành - dễ nói về $n và @values hơn nói về $_[0] và

@_[1..$#_] nhiều lắm.

Kết quả của local() là một danh sách gán được, nghĩa

là nó có thể được dùng ở vế bên trái của toán tử gán

mảng. Danh sách này có thể được đặt giá trị khởi đầu

cho từng biến mới được tạo ra. (Nếu bạn không đặt giá

trị cho danh sách này, thì các biến mới bắt đầu với một

giá trị của undef, giống như bất kì biến mới nào khác.)

Điều này có nghĩa là chúng ta có thể tổ hợp hai câu lệnh

đầu của trình con này, bằng cách thay thế:

local ($n, @values) ; ($n, @values) = @_ ; # chặt arg thành giới hạn và giá

trị

bằng local ($n, @values) = @_ ;

Thực ra, đây là một thứ rất đặc thù thông dụng Perl

cũng hệt như khai báo vậy, local() thực sự là một toán tử

thực hiện được. Nếu bạn đặt nó vào bên trong chu trình,

thì bạn sẽ thu được một biến mới cho mỗi lần lặp chu

trình, mà gần như là vô dụng trừ phi bạn thích lãng phí

bộ nhớ và quên mất mình đã tính gì trong lần lặp trước.

Chiến lược lập trình Perl tốt gợi ý rằng bạn nên nhóm tất

cả các toán tử local() của mình vào phần đầu định nghĩa

trình con, trước khi bạn chui vào phần thịt của trình này.

Page 171: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

159

Bài tập

1. Viết một trình con nhận một giá trị số từ 1 tới 9 và

cho lại tên tiếng Anh (như một, hai, hay chín). Nếu

giá trị đưa vào ở ngoài phạm vi này, thì cho lại số

ban đầu thay vì cho tên. Thử nó với một số dữ liệu

vào - có lẽ bạn sẽ phải viết ra một loại khiển trình

nào đó.

2. Lấy chương trình trong bài tập trước, viết một

chương trình nhận hai số và cộng chúng lại, hiển thị

kết quả kiểu “hai cộng hai là bốn.” (Chớ quên viết

hoa từ đầu!)

3. Mở rộng trình con này để cho lại âm chín qua âm

một và không. Thử chương trình này.

Page 172: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

160

Page 173: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

161

9

Các cấu trúc

điều khiển khác

Toán tử last

Trong một số bài tập trước đây bạn có thể đã nghĩ,

“Nếu tôi có được một câu lệnh break của C ở đây, thì đã

xong rồi.” Cho dù bạn không nghĩ như thế, thì hãy cứ để

tôi nói cho bạn về sự tương đương của Perl để thoát sớm

khỏi chu trình : toán tử last.

Toán tử last ngắt khối chu trình bao quanh ở bên

trong nhất, gây ra việc thực hiện tiếp tục với câu lệnh đi

ngay sau khối đó. Chẳng hạn:

while (cái gì đó) { cái gì đó ; cái gì đó ; cái gì đó ;

Trong chương này:

Toán tử last

Toán tử next

Toán tử redo

Khối có nhãn

Bộ thay đổi biểu thức

&&, || và ?: xem như các cấu trúc

điều khiển

Page 174: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

162

if (điều kiện nào đó) { cái gì đó khác ; cái gì đó khác ; last ; # nhảy ra khỏi chu trình while } thêm nữa ; thêm nữa ;

} # last nhảy tới đây

Nếu điều kiện nào đó là đúng, thì cái gì đó khác sẽ

được thực hiện, và thế rồi toán tử last buộc chu trình

while phải kết thúc.

Toán tử last chỉ tính tới khối chu trình, không tính

khối cần để tạo nên kết cấu cú pháp nào đó. Điều này có

nghĩa là khối tạo nên nhánh then của câu lệnh if không

được tính tới - chỉ khối tạo nên for, foreach, while và các

khối ‘trần” mới được tính. (Khối trần là khối không

thuộc phần khác của một kết cấu lớn hơn, như một chu

trình, hay một trình con, hay một câu lệnh if/then/else).

Giả sử tôi muốn xem liệu thông báo thư đã được cất

giữ trong một tệp có là từ tôi hay không. Một thông báo

như vậy có thể giống như là:

From: [email protected] (Randal L. Schwartz) To: [email protected] Date: 01-SEP-93 08:16:24 PM PDT - 0700 Subject: A sample mail message Here’s the body of the mail message. And here is some

more.

Tôi phải duyệt qua thông báo này từ dòng bắt đầu

với From: và rồi để ý liệu dòng này có chứa tên đăng

nhập của tôi hay không, merlyn.

Page 175: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

163

Tôi có thể làm điều đó như thế này:

while (<STDIN>) { # đọc dòng vào if (/^From:/) { #có bắt đầu với From: không? Nếu có... if (/merlyn/) { # nó là từ tôi! print “Email from Randal! It’s about

time!\n”; } last ; # không cần tìm From: nữa, cho nên ra } # kết thúc “if from:” if (/^$/) { # dòng trống ? last ; # nếu đúng, đừng kiểm tra thêm nữa } } # kết thúc while

Lưu ý rằng một khi dòng có chứa From: được tìm

thấy thì chúng ta đi ra khỏi chu trình chính bởi vì tôi

muốn xem chỉ dòng From: đầu tiên. Cũng lưu ý rằng một

đầu đề thư kết thúc tại dòng trống đầu tiên, cho nên

chúng có thể ra khỏi chu trình chính nữa.

Toán tử next

Giống như last, next cũng làm thay đổi luồng thực

hiện theo trình tự thông thường. Tuy nhiên, toán tử next

làm cho việc thực hiện bỏ qua phần còn lại của khối chu

trình được bao bên trong nhất mà không kết thúc khối

này* . Nó được dùng như thế này:

while (cái gì đó) { phần thứ nhất ; phần thứ nhất ;

* Nếu có một khối continue cho chu trình này, mà chúng ta thì chưa

thảo luận tới, thì toán tử next đi tới chỗ bắt đầu của khối continue

thay vì tới cuối khối này. Khá gần.

Page 176: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

164

phần thứ nhất ; if (điều kiện nào đó) { phần nào đó ; phần nào đó ; next ; # nhảy ra khỏi chu trình while } phần khác ; phần khác ; # next tới đây

}

Nếu điều kiện nào đó là đúng, thì phần nào đó được

thực hiện, và phần khác bị bỏ qua.

Lần nữa, khối của một câu lệnh if không được tính

tới như khối chu trình.

Toán tử redo

Cách thứ ba mà bạn có thể nhảy qua trong một khối

chu trình là bằng redo. Toán tử này nhảy tới chỗ bắt đầu

của khối hiện tại (không tính lại biểu thức điều kiện),

kiểu như:

while (cái gì đó) { # redo tới đây

cái gì đó ; cái gì đó ; cái gì đó ;

if (điều kiện nào đó) { phần nào đó ; phần nào đó ; redo ; } phần khác ; phần khác ; phần khác ;

Page 177: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

165

}

Một lần nữa, khối if không được tính tới. Chỉ tính

các khối chu trình.

Lưư ý rằng với redo và last và khối trần, bạn có thể

tạo nên chu trình vô hạn mà đi ra từ giữa, kiểu như:

{ phần bắt đầu ; phần bắt đầu ; phần bắt đầu ;

if (điều kiện nào đó) { last ; } phần sau ; phần sau ; phần sau ; redo ;

}

Điều này sẽ phù hợp cho một chu trình kiểu while mà

cần tới việc có một phần nào đó của chu trình này được

thực hiện như việc khởi đầu trước phép kiểm thử thứ

nhất. (Trong mục “Bộ thay đổi biểu thức”, dưới đây, tôi

sẽ chỉ ra cho bạn cách viết câu lệnh if với ít kí tự ngắt

hơn.)

Khối có nhãn

Điều gì xảy ra nếu bạn muốn nhảy ra khỏi một khối

có chứa khối bên trong nhất, hay nói theo cách khác, ra

khỏi hai khối lồng nhau ngay một lúc? Trong C, bạn phải

viện tới toán tử goto để đi ra. Không cần phải làm như

vậy trong Perl - bạn có thể dùng last, next và redo tại bất

kì khối kết nào bằng việc cho khối một cái tên có nhãn.

Page 178: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

166

Nhãn là một kiểu tên khác từ một không gian tên

khác mà tuân theo cùng qui tắc như vô hướng, mảng,

mảng kết hợp và trình con. Tuy nhiên, như chúng ta

thấy, một nhãn không có kí tự ngắt đi đầu đặc biệt (như

$ cho vô hướng, & cho trình con, vân vân), cho nên một

nhãn có tên print sẽ xung đột với từ dành riêng print và sẽ

không được phép. Bởi lí do này, Larry gợi ý chọn các

nhãn bao gồm toàn chữ hoa và số, mà anh ấy đảm bảo sẽ

không bao giờ bị chọn nhầm thành một từ dành riêng

trong tương lai. Bên cạnh đó, tất cả các chữ hoa cho

phép dễ nhìn thấy hơn trong một văn bản chương trình

mà phần lớn là chữ thường.

Một khi bạn đã chọn cẩn thận nhãn, nó sẽ đứng ngay

trước câu lệnh có chứa khối, theo sau dấu hai chấm, kiểu

như thế này:

SOMELABEL: while (điều kiện) { câu lệnh ; câu lệnh ; câu lệnh ; if (điều kiện khác) { last SOMELABEL ; } }

Lưư ý rằng tôi đã thêm SOMELABEL, như một

tham biến vào câu lệnh last. Tham biến này bảo cho Perl

ra khỏi khối có tên SOMELABEL, thay vì ra khỏi khối

bên trong nhất. Trong trường hợp này, chúng ta không

có cái gì khác ngoài khối bên trong nhất. Nhưng giả sử

tôi có các chu trình lồng nhau:

OUTER: for ($i = 1; $i <= 10 ; $i++) { INNER: for ($j = 1 ; $j >= 10 ; $j++) { if ($i + $j == 63) {

Page 179: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

167

print “$i lần $j là 63!\n” ; last OUTER; } if ($j >= $i) { next OUTER ; } } }

Tập hợp các câu lệnh này thử tất cả các giá trị kế

tiếp của hai số nhỏ nhất được nhân với nhau cho tới khi

nó tìm ra một cặp có tích là 63 (7 và 9). Lưu ý rằng một

khi đã tìm được một cặp thì không cần phải kiểm tra các

số khác nữa, cho nên câu lệnh if thứ nhất ra khỏi cả hai

chu trình for bằng việc dùng last với nhãn. Câu lệnh if thứ

hai cố gắng đảm bảo rằng số lớn hơn trong hai số bao

giờ cũng là số thứ nhất bằng việc bỏ qua việc lặp tiếp

của chu trình bên ngoài ngay khi điều kiện này không

còn xảy ra nữa. Điều này có nghĩa là các số sẽ được

kiểm thử với ($i, $j) là (1,1), (2,1), (2,2), (3,1), (3,2),

(3,3), (4,1) vân vân.

Cho dù khối bên trong nhất được gắn nhãn, thì các

toán tử last, next, và redo không có tham biến tuỳ chọn

(nhãn) vẫn vận hành tôn trọng khối bên trong nhất. Cũng

vậy, bạn không thể dùng nhãn để nhảy vào trong một

khối - chỉ để nhảy ra khối. Các toán tử last, next hay redo

phải ở bên trong khối.

Bộ thay đổi biểu thức

Xem như một cách khác để chỉ ra “nếu thế này, thì

thế kia,” Perl cho phép bạn gắn nhãn cho một bộ sửa đổi

if lên một biểu thức vốn là một biểu thức đứng riêng.

Page 180: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

168

Kiểu như:

biểu thức nào đó if biểu thức điều khiển ;

Trong trường hợp này, biểu thức điều khiển được tính

trước để xét giá trị chân lí của nó (bằng việc dùng cùng

qui tắc như thường lệ), và nếu đúng, thì biểu thức nào đó

sẽ được tính tiếp. Điều này đại thể tương đương với:

if (biểu thức điều khiển nào đó) { biểu thức nào đó ; }

ngoại trừ rằng bạn không cần thêm dấu ngắt phụ,

câu lệnh này đọc ngược lại, và biểu thức phải là một biểu

thức đơn (không phải là một khối câu lệnh). Tuy nhiên,

nhiều lần cách mô tả ngược này biến thành cách tự nhiên

nhất để phát biểu vấn đề, trong khi cũng tiết kiệm được

vài lần gõ. Chẳng hạn, sau đây là cách bạn có thể ra khỏi

chu trình khi một điều kiện nào đó nảy sinh:

LINE: while (<STDIN>) { last LINE if /^From: / ; }

Bạn xem dễ viết làm sao. Và bạn thậm chí còn có

thể đọc nó theo kiểu tiếng Anh: “dòng cuối nếu nó bắt

đầu với From.”

Các dạng song song khác bao gồm những dạng sau:

exp2 unless exp1; # giống: unless (exp1) { exp2 ; } exp2 while exp1; # giống: while (exp1) { exp2 ; } exp2 until exp1; # giống: util (exp1) { exp2 ; }

Lưư ý rằng tất cả các dạng này đã tính exp1 trước rồi

dựa trên đó, tính hay không tính cái gì đó với exp2.

Chẳng hạn, sau đây là cách tìm ra luỹ thừa đầu tiên

Page 181: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

169

của hai số lớn hơn một số đã cho:

chop ($n = <STDIN>) ; $i = 1; # khởi đầu $i *= 2 until $i > $n ; # lặp cho tới khi tìm ra nó.

Các dạng này không lồng nhau - bạn không thể nói

được exp3 while exp2 if exp1. Điều này là vì dạng exp2

if exp1 không còn là một biểu thức, mà là một câu lệnh

hoàn toàn, và bạn không thể gắn thêm một trong các bộ

sửa đổi này vào sau câu lệnh.

&&, || và ?: xem như các cấu trúc điều khiển

Những cấu trúc này trông tựa như các kí tự ngắt, hay

một phần của biểu thức. Liệu chúng có thể thực sự được

coi là các cấu trúc điều khiển không? Thế này, theo cách

nghĩ Perl, gần như bất kì cái gì cũng đều có thể cả, cho

nên xem điều tôi nói ở đây.

Thông thường, bạn bắt gặp “nếu cái này, thì cái nọ.”

Trước đây chúng ta đã thấy hai dạng này:

if (cái này) { cái nọ ; } # một cách cái nọ if cái này ; # cách khác

Đây là cách thứ ba (và tin tôi đi, vẫn còn nữa đấy):

cái này && cái nọ ;

Tại sao nó lại làm việc? Nó chẳng phải là toán tử

logic và sao? kiểm tra xem cái gì xảy ra khi cái này lấy

giá trị đúng hay sai:

Nếu cái này là đúng, thế thì giá trị của toàn bộ biểu

thức vẫn còn chưa được biết tới, vì nó phụ thuộc vào

giá trị của cái nọ. Cho nên cái nọ phải được tính.

Page 182: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

170

Nếu cái này là sai, thế thì chẳng cần gì mà nhìn vào

cái nọ nữa, bởi vì giá trị của toàn bộ biểu thức phải là

sai rồi. Vì chẳng cần gì phải tính cái nọ nên chúng ta

có thể bỏ qua.

Và thực ra, đây là điều mà Perl làm. Perl tính cái nọ

chỉ khi cái này là đúng, làm cho nó thành tương đương

với hai dạng trước.

Giống thế, toán tử logic hoặc giống như câu lệnh

unless (hay bộ sửa đổi unless). Cho nên bạn có thể thay

thế:

unless (cái này) { cái nọ ; } bằng cái này || cái nọ ;

Nếu bạn quen thuộc với việc dùng các toán tử này

trong lớp vỏ để kiểm soát các chỉ lệnh thực hiện điều

kiện, thì bạn sẽ thấy rằng chúng vận hành tương tự trong

Perl.

Cuối cùng toán tử ba ngôi kiểu C:

exp1 ? exp2 : exp3 ;

tính exp2 nếu exp1 đúng, ngược lại tính exp3. Cũng

dường như là chúng nói:

if (exp1) { exp2 ; } else { exp3 ; }

nhưng một lần nữa không có tất cả các dấu ngắt đó.

Chẳng hạn, bạn có thể viết:

($a < 10) ? $b = $a : $a = $b ;

nên dùng cái nào đây? Tuỳ vào tâm trạng bạn thôi,

đôi khi, hay tuỳ theo từng phần biểu thức lớn đến đâu,

hay liệu cần thêm đóng mở ngoặc nào bởi vì xung đột

Page 183: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

171

thứ tự ưu tiên. Nhìn vào chương trình của người khác và

xem chúng làm gì. Có lẽ bạn sẽ thấy đôi điều ở đó. Larry

gợi ý rằng nên đặt phần quan trọng nhất của biểu thức

lên trước, để cho nó nổi bật ra.

Bài tập

1. Mở rộng bài toán của chương trước để lặp lại phép

toán đó cho tới khi từ end được đưa vào cho một

trong các giá trị. (Hướng dẫn: dùng một chu trình vô

hạn, và rồi thực hiện last nếu giá trị đưa vào là end.)

Page 184: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

172

Page 185: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

173

10

Tước hiệu tệp

và kiểm thử tệp

Tước hiệu tệp là gì?

Tước hiệu tệp là tên trong một chương trình Perl

dành cho việc nối giữa tiến trình Perl của bạn và thế giới

bên ngoài. Chúng ta đã thấy và dùng tước hiệu tệp một

cách không tường minh: STDIN là một tước hiệu tệp, đặt

tên cho việc nối giữa tiến trình Perl và lối vào chuẩn của

UNIX. Giống như vậy, Perl cung cấp STDOUT (cho lối

ra chuẩn) và STDERR (cho lối ra chuẩn cho lỗi). Những

tên này là trùng với các tên được dùng trong bộ trình thư

viện “vào/ra chuẩn” của UNIX, Perl dùng chúng cho hầu

Trong chương này:

Tước hiệu tệp là gì?

Mở và đóng tước hiệu tệp

die()

Dùng tước hiệu

Kiểm thử tệp -x

Các toán tử stat() và lstat()

Dùng tước hiệu tệp

Page 186: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

174

hết việc vào/ra.

Tên tước hiệu tệp cũng giống như tên dành cho các

khối có nhãn, nhưng chúng đến từ một không gian tên

khác (cho nên bạn có thể có một vô hướng $fred, một

mảng @fred, một mảng kết hợp %fred, một chương trình

con &fred, một nhãn fred, và bây giờ một tước hiệu tệp

fred). Giống như nhãn khối, tước hiệu tệp được dùng

không cần một kí tự đặc biệt đứng trước, và do vậy có

thể bị lẫn lộn với các từ dành riêng hiện có hay trong

tương lai. Một lần nữa, khuyến cáo của Larry là dùng

TẤT CẢ CÁC CHỮ HOA trong tước hiệu tệp của mình

- không chỉ nó biểu thị tốt hơn, mà nó cũng sẽ đảm bảo

rằng chương trình của bạn sẽ không hỏng khi các từ

dành riêng tương lai được đưa vào.

Mở và đóng một tước hiệu tệp

Perl cung cấp ba tước hiệu tệp, STDIN, STDOUT,

STDERR, tự động mở cho các tệp hay thiết bị do tiến

trình cha mẹ của chương trình này đã thiết lập (có thể là

lớp vỏ). Bạn dùng toán tử open() để mở các tước hiệu tệp

phụ, hệt như bạn làm trong chương trình được viết trong

C. Cú pháp của nó giống thế này:

open (FILEHANDLE, “tên nào đó”);

với FILEHANDLE là tước hiệu tệp mới, còn tên

nào đó là tên tệp UNIX ngoài (như một tệp hay thiết bị)

mà sẽ được liên kết với tước hiệu tệp mới. Việc gọi này

mở tước hiệu tệp để đọc. Việc mở một tệp để ghi cũng

dùng cùng toán tử open, nhưng phần tiền tố của tên tệp

có một dấu lớn hơn (như trong vỏ):

Page 187: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

175

open (OUT, “>outfile”);

Chúng ta sẽ thấy trong mục “Dùng tước hiệu tệp,”

dưới đây cách sử dụng tước hiệu tệp này. Cũng vậy, như

trong vỏ, bạn có thể mở một tệp để thêm vào sau bằng

việc dùng hai dấu lớn hơn làm tiền tố, như trong:

open (LOGFILE, “>>mylogfile”);

Tất cả ba dạng này của open đã cho lại đúng nếu

việc mở thành công và sai nếu thất bại. (Việc mở một tệp

đưa vào sẽ sai, chẳng hạn, nếu tệp đó không có hay

không thể truy nhập tới được bởi không được phép; việc

mở tệp đưa ra sẽ sai nếu danh mục không cho ghi hay

không cho truy nhập tới.)

Khi bạn kết thúc với một tước hiệu tệp, bạn có thể

đóng nó bằng toán tử close, tựa như:

close(LOGFILE);

Việc mở lại một tước hiệu tệp cũng làm đóng tệp

mở trước đó một cách tự động, cũng như khi ra khỏi

chương trình. Vì điều này, phần lớn các chương trình

Perl không bận tâm với close(). Nhưng nó vẫn có đó nếu

bạn muốn được chặt chẽ hay chắc chắn rằng tất cả dữ

liệu đã được đẩy ra hết đôi khi sớm hơn việc kết thúc của

chương trình.

Một chút tiêu khiển: die()

Coi đây như là một chú thích cuối trang lớn, nhưng

lại nằm ở giữa trang.

Một tước hiệu tệp mà không được mở thành công thì

có thể vẫn được dùng mà thậm chí không gây ra cảnh

Page 188: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

176

báo gì nhiều lắm trong toàn bộ chương trình. Nếu bạn

đọc từ tước hiệu tệp, bạn sẽ nhận được ngay cuối tệp.

Nếu bạn ghi lên tước hiệu tệp, dữ liệu cứ im ỉm bị bỏ đi

(giống như lời hứa hẹn vận động bầu cử năm trước).

Thường bạn muốn kiểm tra lại kết quả của việc mở

tệp và báo cáo lại lỗi nếu kết quả không phải là điều bạn

dự kiến. Chắc chắn, bạn có thể rải rác trong chương trình

của mình với những thứ kiểu như:

unless (open(DATAPLACE, “>/tmp/dataplace”)) { print “Rất tiếc, tôi không thể tạo được

/tmp/dataplace\n”; } else { # phần còn lại chương trình của bạn }

Nhưng đấy là cả đống việc. Và điều thường xẩy ra

với Perl là đưa ra một lối tắt. Toán tử die() lấy một danh

sách bên trong dấu ngoặc tròn tuỳ chọn, phun ra danh

sách đó (giống như print) trên lối ra lỗi chuẩn, và rồi kết

thúc tiến trình Perl (tiến trình đang chạy chương trình

Perl) với một trạng thái ra khác không của UNIX (nói

chung chỉ ra một cái gì đó bất thường xẩy ra). Cho nên,

viết lại đoạn mã trên thì sẽ thấy nó giống như thế này:

unless (open(DATAPLACE, “>/tmp/dataplace”)) { die “Rất tiếc, tôi không thể tạo được

/tmp/dataplace\n”; } # phần còn lại chương trình của bạn

Nhưng chúng ta thậm chí còn có thể đi thêm một

bước nữa. Nhớ rằng có thể dùng toán tử (logic hoặc) || để

làm ngắn thêm điều này, như trong

unless (open(DATAPLACE, “>/tmp/dataplace”)) || die “Rất tiếc, tôi không thể tạo được

Page 189: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

177

/tmp/dataplace\n”;

Vậy die sẽ được thực hiện chỉ khi kết quả của open

là sai. Cách thông dụng để đọc điều này là “mở tệp đó ra

nếu không thì chết quách đi cho rồi!” và đó là cách dễ

dàng để nhớ bất kì khi nào dùng toán tử logic và hay

logic hoặc.

Thông báo vào lúc chết (được xây dựng từ đối của

die) có tên chương trình Perl và số dòng được gắn tự

động vào, cho nên bạn có thể dễ dàng xác định được die

nào trong chương trình của bạn chịu trách nhiệm cho

việc ra không đúng lúc này. Nếu bạn không thích số

dòng hay tệp bị lộ ra, thì phải chắc chắn rằng văn bản

chết có một dấu dòng mới ở cuối. Chẳng hạn:

die “bạn nấu nước xốt - lợn sữa”;

sẽ in ra tên tệp và số dòng, trong khi

die “bạn nấu nước xốt - lợn sữa\n”;

thì không in ra tên tệp và số dòng.

Dùng tước hiệu tệp

Một khi tước hiệu tệp được mở ra để đọc, bạn có thể

đọc các dòng từ nó hệt như bạn có thể đọc từ lối vào

chuẩn với STDIN. Vậy, chẳng hạn, để đọc các dòng từ

tệp mật hiệu:

open (EP, “/etc/passwd”); while (<EP>) { chop; print “Tôi thấy $_ trong tệp mật hiệu!\n”; }

Page 190: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

178

Lưu ý rằng tước hiệu tệp mới mở được dùng bên

trong dấu ngoặc nhọn hệt như đã dùng STDIN trước đây.

Một tước hiệu tệp mở ra để ghi hay hiệu đính đã

phải được cho như một đối của toán tử print, xuất hiện

ngay sau từ khoá print nhưng trước danh sách đối:

print LOGFILE “Khoản mục kết thúc của $max\n”; print STDOUT “Xin chào, mọi người!\n”; # giống như in

“xin chào mọi người!\n”

Trong trường hợp này, thông báo bắt đầu với Khoản

mục kết thúc sẽ ghi lên tước hiệu tệp LOGFILE, mà giả

thiết là đã mở trước đây trong chương trình. Và xin chào

mọi người sẽ đi ra lối ra chuẩn, hệt như khi bạn không

xác định tước hiệu tệp. Chúng ta nói rằng STDOUT là

tước hiệu xử lí tệp ngầm định cho câu lệnh print.

Vậy, tóm lại, sau đây là cách để sao chép tất cả văn

bản từ một tệp được xác định trong $a vào một tệp được

xác định trong $b. Nó minh hoạ gần như mọi thứ mà đã

học trong vài trang vừa qua:

open (IN,$a) || die “không thể mở được $a để đọc”; open (OUT, “>$b”) || die “không thể tạo dược $b”; while (<IN>) { # đọc một dòng từ tệp $a vào $_ print OUT $_; # in dòng đó vào tệp $b } close(IN); close(OUT);

Kiểm tra tệp -x

Bây giờ bạn đã biết cách để mở một tước hiệu tệp để

ghi ra, viết đè lên bất kì tệp hiện có nào với cùng tên.

Giả sử bạn muốn chắc chắn rằng không có một tệp nào

Page 191: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

179

với tên đó (để giữ cho bạn khỏi ngẫu nhiên làm mất tiêu

dữ liệu bảng tính hay lịch ngày sinh quan trọng). Nếu

bạn định viết một bản ghi vỏ thì bạn nên dùng cái gì đó

tựa như -e tên tệp để kiểm tra liệu tệp đó có tồn tại hay

không. Tương tự thế, Perl dùng -e $filevar để kiểm tra sự

tồn tại của tệp mang tên bởi giá trị vô hướng $filevar. Nếu

tệp này tồn tại thì kết quả là đúng; ngược lại nó là sai.

Chẳng hạn:

$x = “/etc/passwd”; if (-e $x) { # liệu /etc/passwd có tồn tại không? # tốt } else { print “how in the world did you get logged in?\n”; }

Toán hạng của toán tử -e thực sự là bất kì biểu thức

vô hướng nào tính một xâu nào đó, kể cả một xâu hằng.

Sau đây là một thí dụ kiểm tra cho cả mật hiệu hệ thống

và tệp nhóm:

if (-e “/etc/passwd && -e “/etc/group”) { print “looks like you have a normal system\n”;

Các toán tử khác cũng được xác định rõ. Chẳng hạn,

-r $filevar cho lại giá trị đúng nếu tệp đã có tên trong

$filevar đang tồn tại và đọc được. Tương tự, -w $filevar

kiểm tra xem liệu có ghi được được không. Sau đây là

một thí dụ kiểm tra tên tệp do người dùng xác định cho

cả tính đọc được và ghi được:

print “ở đâu? “; $filename = <STDIN>; chop($filename); # quẳng cái dấu dòng mới khó chịu đi if (-r $filename && -w $filename) { # tệp đã có, và tôi có thể đọc và ghi nó ...

Page 192: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

180

}

Nhiều việc kiểm tra tệp đã có sẵn. Xin xem Bảng

10-1 để biết danh sách đầy đủ.

Bảng 10.1: Kiểm tra tệp và ý nghĩa của chúng

Kiểm

tra tệp

Ý nghĩa

-r Tệp hay danh mục đọc được

-w Tệp hay danh mục ghi được

-x Tệp hay danh mục thực hiện được

-o Tệp hay danh mục do người dùng sở

hữu

-R Tệp hay danh mục đọc được bởi

người dùng thực, không phải người

dùng hiệu quả (khác -r với chương

trình setuid)

-W Tệp hay danh mục ghi được bởi người

dùng thực, không phải người dùng

hiệu quả (khác với -w cho chương

trình setuid)

-X Tệp hay danh mục thực hiện được bởi

người dùng thực, không phải người

dùng hiệu quả (khác với -x cho

chương trình setuid)

-O Tệp hay danh mục được sở hữu bởi

người dùng thực, không phải người

dùng hiệu quả (khác với -o cho

chương trình setuid)

-e Tệp hay danh mục đã có

-z Tệp đã có và có kích thước không

Page 193: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

181

Kiểm

tra tệp

Ý nghĩa

(danh mục thì không bao giờ rỗng)

-s Tệp hay danh mục đã có và có kích

thước khác không (giá trị được tính

theo byte)

-f Khoản mục là tệp rõ

-d Khoản mục là danh mục

-l Khoản mục là symlink

-S Khoản mục là chỗ cắm

-p Khoản mục là đường ống có tên (một

“fifo”)

-b Khoản mục là tệp khối đặc biệt (giống

như đĩa tháo lắp được)

-c Khoản mục là tệp kí tự đặc biệt (như

thiết bị vào/ra)

-u Tệp hay danh mục là setuid

-g Tệp hay danh mục là setgid

-k Tệp hay danh mục có tập bit dính

-t isatty() trên tước hiệu tệp là đúng

-T Tệp là văn bản

-B Tệp là “nhị phân”

-M sửa tuổi theo ngày

-A Tuổi truy nhập theo ngày

-C Tuổi thay đổi inode theo ngày

Phần lớn trong những phép kiểm tra này đã cho lại

một điều kiện đúng-sai đơn giản. Số ít thì không, cho

nên hãy nói về chúng.

Toán tử -s không cho lại giá trị đúng nếu tệp là khác

rỗng, nhưng giá trị cho lại là một loại đúng đặc biệt. đó

Page 194: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

182

là chiều dài theo byte của tệp, vẫn được coi là đúng đối

với một số khác không.

Toán tử tuổi -M, -A và -C (đúng, chúng đã là chữ

hoa cả) cho lại số ngày kể từ tệp được sửa đổi, truy nhập

hay có thay đổi inode* lần cuối. (inode chứa tất cả các

thông tin về tệp ngoại trừ nội dung của nó - xem chi tiết

trong lời gọi hệ thống stat). Giá trị tuổi này là phân số

với độ phân giải một giây - 36 giờ được cho lại là 1.5

ngày. Nếu bạn so sánh tuổi này với toàn bộ số (chẳng

hạn ba), bạn sẽ thu được chỉ các tệp đã bị thay đổi đúng

nhiều ngày trước đây, không nhiều hay ít hơn một giây.

Điều này có nghĩa là có lẽ bạn sẽ muốn có việc so sánh

theo phạm vi (hay toán tử int()) hơn là so sánh chính xác

để được các tệp nằm giữa ba và bốn ngày lẻ.

Tất cả những toán tử này có thể vận hành trên tước

hiệu tệp cũng như tên tệp. Với tước hiệu tệp làm toán

hạng là tất cả những gì toán tử đó cần. Vậy để kiểm tra

xem liệu tệp có được mở như SOMEFILE có là thực

hiện được hay không, bạn có thể dùng:

if (-x SOMEFILE) { # mở tệp trên SOMEFILE là thực hiện được }

Nếu bạn để tham biến tên tệp hay tước hiệu tệp bỏ

không (tức là, bạn chỉ có -r hay -s) thì toán hạng mặc

định là tệp có tên trong biến $_ (nó vẫn có đấy!). Cho

nên, để kiểm thử một danh sách các tên tệp xem tệp nào

* Tuổi được đo tương đối theo thời gian chương trình bắt đầu, như

được lấy theo thời gian UNIX trong biến S^T. Có thể lấy được số

âm cho những tuổi này nếu giá trị hỏi tham chiếu tới một sự kiện đã

xẩy ra sau khi chương trình bắt đầu.

Page 195: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

183

đọc được, thì chỉ cần đơn giản là:

foreach (@some_list_of_filenames) { print “”$_ là đọc được\n” if -r; # cũng như -r $_ }

Các toán tử stat() và lstat()

Trong khi các phép kiểm tra tệp này là tốt cho việc

kiểm tra nhiều thuộc tính liên quan tới một tệp hay danh

mục đặc biệt, thì chúng lại không nói được toàn bộ câu

chuyện. Chẳng hạn, không có việc kiểm tra tệp nào cho

lại số các liên kết với một tệp. Để thu được thông tin còn

lại về tệp, đơn thuần gọi tới toán tử stat(), toán tử cho lại

đủ mọi thứ mà lời gọi hàm hệ thống UNIX stat() cho (hi

vọng nhiều thứ hơn điều bạn muốn biết).

Toán hạng của stat() là tước hiệu tệp, hay một biểu

thức tính cho tên tệp. Giá trị cho lại hoặc là undef, chỉ ra

rằng stat không sinh được, hay một mảng 13 giá trị, phần

lớn đã dễ mô tả bằng việc dùng danh sách sau đây các

biến vô hướng:

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,

$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat(...)

Các tên ở đây đã trỏ tới các bộ phận của cấu trúc

stat, được mô tả chi tiết trong stat(2) của bạn. Có lẽ bạn

nên nhìn vào đó để xem các mô tả chi tiết.

Chẳng hạn để lấy ID (số hiệu) người dùng và ID

nhóm của tệp mật hiệu, thử:

($uid,$gid) = (stat (“/etc/passwd”)) [4, 5];

Và đó là cách làm.

Page 196: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

184

Gọi toán tử stat() trên tên của một liên kết kí hiệu sẽ

cho lại thông tin về liên kết kí hiệu này đang trỏ tới cái

gì, không phải thông tin về bản thân liên kết kí hiệu (trừ

phi liên kết này xẩy ra để không trỏ vào cái gì hiện thời

truy nhập được). Nếu bạn cần thông tin (phần lớn vô

dụng) về bản thân liên kết kí hiệu, dùng lstat() thay vì

stat() (cho cùng thông tin theo cùng thứ tự). Toán tử

lstat() làm việc tựa như stat() trên những điều không phải

là liên kết kí hiệu.

Giống như việc kiểm tra tệp, toán hạng của stat hay

lstat mặc định là $_, nghĩa là stat sẽ được thực hiện trên

tệp có tên trong biến vô hướng $_.

Dùng _Filehandle

Mọi lần bạn nói stat(), -r, -w hay bất kì cái gì trong

chương trình, Perl đã phải trở ra hệ thống để hỏi bộ đệm

stat trên tệp (bộ đệm cho lại từ lời gọi hệ thống stat).

Điều đó có nghĩa là nếu bạn muốn biết liệu tệp có vừa

đọc được và ghi được không, bạn về bản chất đã hỏi hệ

thống hai lần cho cùng một thông tin (điều này không

thể thay đổi được trong một môi trường khá không thân

thiện)

Điều này có vẻ như lãng phí, và thực ra, có thể tránh

được. Thực hiện việc kiểm tra tệp, stat, hay lstat trên

_filehandle (một dấu gạch thấp) xem như toán hạng sẽ

bảo cho Perl dùng bất kì cái gì ngẫu nhiên có trong bộ

nhớ từ lần kiểm tra tệp trước đó. Đôi khi điều này là

nguy hiểm: một chương trình con có thể gọi stat một

cách không chủ định, ném tiêu bộ đệm của bạn đi.

Nhưng nếu bạn cẩn thận, bạn có thể tiết kiệm một vài lời

Page 197: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

185

gọi hệ thống không cần thiết. Sau đây là thí dụ nữa về

việc kiểm tra tính ghi được và đọc được $filevar, dùng

mẹo mới:

if (-r $filevar && -w _) { print “$filevar là vừa đọc được và ghi được\n”; }

Lưu ý rằng tôi đã dùng $filevar cho phép kiểm tra

lần thứ nhất - điều này lấy dữ liệu từ hệ điều hành. Lần

kiểm tra thứ hai dùng _filehandle ảo thuật; với phép

kiểm tra này, bản thân dữ liệu bị bỏ lại từ phép kiểm tra

$filevar về tính đọc được nay lại được dùng, đúng là điều

mong muốn.

Chú ý rằng việc kiểm thử _filehandle không hệt như

việc cho phép toán hạng của việc kiểm tra tệp, stat, hay

lstat được mặc định kiểm tra $_; điều này sẽ là việc kiểm

tra mới mỗi lần trên tệp hiện tại mang tên theo nội dung

của $_. Đây lại là một trường hợp khác khi các tên tương

tự đuợc chọn cho các chức năng khá khác nhau. Hiện tại,

bạn có lẽ đã quen với nó.

Bài tập

Xem phụ lục A về lời giải.

1. Viết một chương trình để đọc vào một tên tệp từ

STDIN, rồi mở tệp đó và hiển thị nội dung của nó có

đứng trước bởi tên tệp và một dấu hai chấm. Chẳng

hạn, nếu fred được đọc vào, và tệp fred bao gồm ba

dòng aaa, bbb và ccc, bạn sẽ thấy fred: aaa, fred: bbb

và fred: ccc.

Page 198: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

186

2. Viết một chương trình nhắc đưa vào một tên tệp vào,

một tên tệp ra, một mẫu tìm kiếm, và một xâu thay

thế, rồi thay thế tất cả mọi lần xuất hiện của mẫu tìm

kiếm bằng xâu thay thế trong khi sao tệp vào sang

tệp ra. Thử nó trên các tệp. Bạn có thể ghi đè một tệp

hiện có (đừng thử nó với những tệp quan trọng!)

không? Bạn có thể dùng các kí tự biểu thức chính qui

trong xâu tìm kiếm không? Bạn có thể dùng \1 trong

xâu thay thế không?

3. Viết một chương trình để đọc vào một danh sách các

tên tệp và rồi cho hiển thị từ đó các tệp nào là đọc

được, ghi được, và/hoặc thực hiện được, và tệp nào

không tồn tại. (Bạn có thể thực hiện từng phép kiểm

thử cho từng tên tệp khi bạn đọc chúng; hay trên toàn

bộ tập các tên khi bạn đã đọc tất cả chúng. Đừng

quên loại bỏ dấu dòng mới tại cuối mỗi tên tệp mà

bạn đã đọc vào.

4. Viết một chương trình để đọc vào một danh sách các

tên tệp, và tìm tệp cũ nhất trong chúng. In ra tên của

tệp đó, tuổi của nó theo số ngày.

Page 199: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

187

11

Dạng thức

Dạng thức là gì?

Trong số nhiều việc làm được, Perl thường được

dùng làm “ngôn ngữ trích rút và báo cáo thực hành.”

Đây là lúc biết về việc “ngôn ngữ báo cáo đó”.

Perl cung cấp một khái niệm về khuôn mẫu viết báo

cáo đơn giản, được gọi là dạng thức. Dạng thức xác định

ra phần không đổi (tiêu đề cột, nhãn, văn bản cố định

hay bất kì cái gì) và phần biến đổi (dữ liệu hiện tại mà

bạn báo cáo). Hình dạng của dạng thức, rất gần với hình

dạng của cái ra, tương tự như cái ra đã được dạng thức

trong COBL hay mệnh đề print using của một số ngôn

ngữ BASIC.

Việc dùng dạng thức bao gồm ba điều sau:

1. Định nghĩa dạng thức

Trong chương này:

Dạng thức là gì?

Gọi một dạng thức

Nói thêm về Fieldholder

Dạng thức đầu trang

Đổi mặc định cho dạng thức

Page 200: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

188

2. Nạp dữ liệu cần in vào phần biến đổi của dạng

thức (trường)

3. Gọi tới dạng thức

Thường, bước thứ nhất được thực hiện ngay (trong

văn bản chương trình sao cho nó được xác định vào lúc

dịch), và hai bước sau được thực hiện lặp đi lặp lại.

Định nghĩa một dạng thức

Dạng thức được định nghĩa bằng việc dùng định

nghĩa dạng thức. Định nghĩa dạng thức này có thể xuất

hiện ở bất kì đâu trong văn bản chương trình của bạn,

giống như chương trình con. Định nghĩa dạng thức trông

tựa như thế này:

format têndạngthức =

dòngtrường

giá_trị_một, giá _trị_hai, giá _trị_ba

dòngtrường

giá_trị_một, giá _trị_hai, giá _trị_ba

dòngtrường

giá_trị_một, giá _trị_hai, giá _trị_ba

.

Dòng thứ nhất có chứa từ dành riêng format, tiếp đó

là tên dạng thức và rồi đến dấu bằng (=). Tên dạng thức

được chọn từ một không gian tên khác, và tuân theo

cùng qui tắc như mọi thứ khác. Vì tên dạng thức không

bao giờ được dùng bên trong thân chương trình (ngoại

trừ bên trong giá trị xâu), nên bạn có thể an toàn dùng

các tên trùng với với các từ dàng riêng. Như bạn sẽ thấy

trong mục sau, “Gọi dạng thức,” phần lớn các tên dạng

Page 201: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

189

thức của bạn có lẽ sẽ là một như tên tước hiệu tệp (mà

thế, làm cho chúng không phải là một như các từ dành

riêng)

Tiếp theo sau dòng thứ nhất là bản thân khuôn mẫu,

mở rộng từ không đến nhiều dòng văn bản. Phần cuối

của khuôn mẫu được chỉ ra bằng một dấu chấm. Khuôn

mẫu là nhạy cảm với khoảng trắng - đây là một trong vài

chỗ mà một số khoảng trắng (dấu cách, xuống dòng, hay

tab) gây ra vấn đề trong văn bản chương trình Perl.

Định nghĩa khuôn mẫu có chứa một chuỗi các dòng

trường. Mỗi dòng trường có thể chứa văn bản cố định -

văn bản sẽ được in ra theo từng kí tự khi dạng thức này

được gọi tới. Sau đây là một thí dụ về dòng trường có

văn bản cố định:

Hello, my name is Fred Flintstone.

Tên trường có thể chứa cả nơi giữ trường cho văn

bản biến đổi. Nếu một dòng có chứa nơi giữ trường,

dòng tiếp sau của khuôn mẫu (được gọi là dòng giá trị)

sẽ mô tả cho một loạt các giá trị vô hướng - mỗi giá trị

ứng với một nơi giữ trường - mà cung cấp ra giá trị sẽ

được gắn vào trong trường. Sau đây là một thí dụ về

dòng trường với một nơi giữ trường, và dòng giá trị đi

theo:

Hello, my name is @<<<<<<<<<<<.

$name

Nơi giữ trường là @<<<<<<<<<<<, sẽ xác định ra

trường văn bản được dồn trái bởi 11 kí tự. Các chi tiết

đầy đủ hơn về nơi giữ trường sẽ được nêu trong mục có

tên “Nói thêm về nơi giữ trường” dưới đây.

Page 202: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

190

Nếu dòng trường có nhiều nơi giữ trường, nó cần

nhiều giá trị, cho nên các giá trị được tách nhau bởi dấu

phẩy:

Hello, my name is @<<<<<<<<<<< and I’m @<< years old..

$name, $age

Gắn tất cả những điều này lại chúng ta có thể tạo ra

một dạng thức đơn giản cho một nhãn địa chỉ:

format ADDRESSLABEL = ======================== | @<<<<<<<<<<<<<<<<<<<< | $name | @<<<<<<<<<<<<<<<<<<<< | $address | @<<<<<<<<<<<, @< @<<<< | $city, $state, $zip ======================== .

Lưu ý rằng các dòng có dấu bằng trên đỉnh và dưới

đáy của dạng thức không có trường, và do vậy không có

dòng giá trị theo sau. (Nếu bạn đặt một dòng giá trị đi

theo sau dòng trường như vậy, nó sẽ được diễn giải như

một dòng trường khác, có thể không làm điều bạn

muốn.)

Khoảng trống bên trong dòng giá trị bị bỏ qua. Một

số người chọn việc dùng khoảng trống phụ trong dòng

giá trị để nối dòng biến với nơi giữ trường trên dòng

trước đó (như đặt $zip ở dưới trường thứ ba của dòng

trước đó trong thí dụ này), nhưng thế chỉ để mà trông

thôi. Perl không quan tâm tới điều đó, và nó không ảnh

hưởng tới cái ra của bạn.

Page 203: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

191

Các giá trị được tính toán cho các giá trị vô hướng

của chúng, cho nên tất cả các biểu thức đã được diễn giải

theo ngữ cảnh vô hướng* . Văn bản theo sau dấu xuống

dòng thứ nhất trong một giá trị bị bỏ qua (ngoại trừ trong

trường hợp đặc biệt nhiều nơi giữ trường, sẽ được mô tả

về sau).

Định nghĩa dạng thức cũng giống như định nghĩa

chương trình con. Nó không chứa chương trình thực hiện

ngay lập tức, và do đó có thể được đặt ở bất kì đâu trong

tệp với phần còn lại của chương trình - tôi có khuynh

hướng đặt những dạng thức của mình vào cuối tệp, trước

các định nghĩa chương trình con.

Gọi một dạng thức

Bạn gọi tới một dạng thức bằng toán tử write. Toán

tử này lấy tên của tước hiệu tệp, và sinh ra văn bản cho

tước hiệu tệp đó bằng việc dùng dạng thức hiện thời cho

tước hiệu tệp đó. Theo ngầm định, dạng thức hiện thời

cho một tước hiệu tệp là dạng thức với cùng tên (cho nên

với tước hiệu tệp STDOUT, dạng thức STDOUT sẽ

được dùng), nhưng chúng ta sẽ thấy ngay rằng bạn có thể

thay đổi nó.

Lấy một thí dụ khác bằng việc xét dạng thức nhãn

địa chỉ, và tạo ra một tệp chứa các nhãn địa chỉ. Sau đây

là một đoạn chương trình:

format ADDRESSLABEL =

* Trong Perl 5.0, tôi được biết rằng toàn bộ dòng bây giờ được tính

theo ngữ cảnh mảng, cho nên phát biểu này là không đúng. Đáng

phải nói khác đi nêu bạn có điều gì đó như @a là một giá trị.

Page 204: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

192

======================== | @<<<<<<<<<<<<<<<<<<<< | $name | @<<<<<<<<<<<<<<<<<<<< | $address | @<<<<<<<<<<<, @< @<<<< | $city, $state, $zip ======================== . open (ADDRESSLABEL, “>labels-to-print”) || die “can’t

create”; open (ADDRESSLABEL, “addresses”) || die “can not

open addresses”; while ( <ADDRESSES> ) { chop; # remove newline ($name, $address, $city, $state, $zip) = split (/:/) ; # load up the global variables write ADDRESSLABEL; # send the output }

Tại đây chúng ta thấy định nghĩa dạng thức trước,

nhưng bây giờ chúng ta cũng còn có thêm cả chương

trình thực hiện nữa. Trước hết, chúng ta mở một tước

hiệu tệp lên một tệp ra được gọi là labels-to-print. Lưu ý

rằng tên tước hiệu tệp (ADDRESSLABEL) là cùng tên

của dạng thức. Điều này là quan trọng. Tiếp đó, mở tước

hiệu tệp trên danh sách địa chỉ. Dạng thức của danh sách

địa chỉ được giả sử là một cái gì đó tựa như:

Stonehenge:4470 SW Hall Suite 107: Beaverton:OR:97005

Fred Flintstone:3737 Hard Rock Lane:Bedrock:OZ:999bc

Nói cách khác, năm trường tách biệt, mà chương

trình sẽ phân tích như mô tả dưới đây.

Chu trình while trong chương trình này đọc từng

Page 205: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

193

dòng của tệp địa chỉ mỗi lúc, bỏ đi dấu xuống dòng, rồi

chẻ dòng này (không có dấu xuống dòng) vào năm biến.

Lưu ý rằng các tên biến cũng là tên mà đã dùng khi định

nghĩa dạng thức. Điều này nữa cũng là quan trọng.

Một khi có tất cả các biến được nạp vào (để cho các

giá trị được dạng thức sử dụng là đúng đắn), toán tử write

gọi tới dạng thức này. Lưu ý rằng tham biến cho write là

tước hiệu tệp cần ghi ra và theo mặc định dạng thức cho

cùng tên cũng được dùng.

Mỗi trường trong dạng thức đã được thay thế bởi

một giá trị tương ứng từ dòng tiếp của dạng thức. Sau

khi hai bản ghi mẫu được nêu ở trên đã được xử lí, tệp

labels-to-print có chứa:

===================== | Stonehege | | 4470 SW Hall Suite 107 | | Beaverton , OR 97005| ===================== ===================== | Fred Flintstone | | 3737 Hard Rock Lane | | Bedrock , OZ 999bc | =====================

Nói thêm về nơi giữ tệp

Cho đến giờ, qua thí dụ, bạn đã biết rằng nơi giữ

trường @<<<< có nghĩa là một trường được dồn trái với

năm kí tự và rằng @<<<<<<<<<<< nghĩa là một trường

được dồn trái với 11 kí tự. Sau đây là toàn bộ phạm vi,

như đã hứa trước đây.

Page 206: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

194

Trường văn bản

Phần lớn những nơi giữ trường đã bắt đầu bằng @.

Các kí tự đi sau @ chỉ ra kiểu của trường, trong khi số

các kí tự (kể cả @) chỉ ra chiều rộng của trường.

Nếu các kí tự đi sau @ là dấu mở ngoặc góc trái

(<<<<), bạn nhận được một trường được dồn trái - tức

là, giá trị sẽ được gắn thêm bên phải bằng dấu cách nếu

giá trị này ngắn hơn chiều rộng trường. (Nếu một giá trị

quá dài, nó sẽ bị chặt cụt tự động - dạng của dạng thức

bao giờ cũng được bảo tồn.)

Nếu các kí tự đi sau @ là dấu đóng ngoặc góc phải

(>>>>), bạn nhận được một trường được dồn phải - tức

là nếu giá trị quá ngắn, nó sẽ được bổ sung dấu cách vào

bên trái.

Cuối cùng, nếu các kí tự đi sau @ là dấu sổ đứng (| |

| |), bạn nhận được một trường định tâm: nếu giá trị quá

ngắn, nó được bổ sung thêm dấu cách vào cả hai bên, đủ

cho từng bên làm cho giá trị thành định tâm nhất bên

trong trường.

Trường số

Một loại nơi giữ trường khác là trường số độ chính

xác tĩnh, có ích cho những báo cáo tài chính lớn. Trường

này cũng bắt đầu với @, và được theo sau bởi một hay

nhiều dấu # với một dấu chấm tuỳ chọn (chỉ ra dấu chấm

thập phân). Một lần nữa, @ lại được đếm như một trong

các kí tự của trường. Chẳng hạn:

format MONEY Assets: @#####.## Liabilities: @#####.## Net:

Page 207: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

195

@#####.## $assets, $liabilities, $assets-$liabilities .

Ba trường số cho phép sáu vị trí bên trái dấu chấm

thập phân và hai vị trí bên phải (có ích cho đô la Mĩ và

phần xu). Lưu ý tới việc dùng một biểu thức theo dạng

thức - hoàn toàn hợp lệ và thường hay được dùng.

Perl không đưa ra điều gì cho người thành thạo khác

hơn điều này: bạn không thể nào lấy kí hiệu tiền trôi nổi

hay dấu ngoặc nhọn quanh giá trị âm hay bất kì cái gì

khác. Để làm điều đó, bạn phải viết chương trình con của

riêng mình, kiểu như:

format MONEYCOOL = Assets: @#####.## Liabilities: @#####.## Net:

@#####.## &cool ($assets, 10), &cool ($liabilities, 9) , &cool

($assets-$liabilities, 10) . sub cool { local ($n, $width) = @_ ; $width -= 2 ; # back off for negative stuff $n = sprintf (“%.2f”, $n) ; # sprintf is in later chapter if ($n < 0) { sprintf (“[%$width.2f]”, - $n) ; # negative numbers get spaces

instead } } ## body of program: $assets = 32125.12; $liab = 45212.15; write

MONEYCOOL;

Page 208: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

196

Trường nhiều dòng

Như đã nói trước đây, Perl thông thường dùng tại

dấu xuống dòng đầu tiên của một giá trị khi đặt kết quả

vào đầu ra. Một loại nơi chứa trường, nơi chứa trường

nhiều dòng, cho phép bạn đưa vào một giá trị mà có thể

có nhiều dòng thông tin. Nơi chứa trường này được kí

hiệu bởi @* trên một dòng bởi chính nó - bao giờ cũng

vậy, dòng đi theo sau xác định ra giá trị mà sẽ được thế

vào trong trường này, mà trong trường hợp này có thể là

một biểu thức cho kết quả có chứa trên nhiều dòng.

Giá trị được thế vào sẽ trông hệt như văn bản gốc:

bốn dòng của giá trị trở thành bốn dòng của cái ra.

Chẳng hạn:

format STDOUT = Text Before. @* $long_string Text After. . $long_string = “Fred\nBaney\nBetty\nWilma\n”; write ;

sinh ra cái ra:

Text Before. Fred Baney Betty Wilma Text After.

Trường được lấp đầy

Một loại nơi chứa trường khác là trường được lấp

Page 209: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

197

đầy. Nơi chứa trường này cho phép bạn tạo ra một đoạn

được lấp đầy, bẻ văn bản thành các dòng có kích cỡ qui

ước tại biên giới từ, bao bọc dòng nếu cần. Có vài phần

cùng làm việc ở đây, nhưng chúng ta hãy xét chúng một

cách tách biệt.

Trước hết, một trường được lấp đầy được kí hiệu

bằng việc thay thế dấu hiệu @ trong nơi chứa trường văn

bản bởi dấu mũ (vậy bạn nhận được ^<<<<, chẳng hạn).

Giá trị tương ứng cho trường được lấp đầy (trên dòng

tiếp của dạng thức) phải là một biến vô hướng có chứa

văn bản, thay vì một biểu thức cho lại một giá trị vô

hướng. Lí do cho điều này là ở chỗ Perl sẽ thay đổi biến

này trong khi rót đầy trường được lấp, và cũng hơi khó

để mà thay đổi một biểu thức.

Khi Perl rót đầy trường được lấp, nó lấy giá trị của

biến và vét lấy nhiều từ (bằng việc dùng một định nghĩa

hợp lí về “từ”)* đủ khít vào trong trường. Những từ này

thực tế vượt ra ngoài biến - giá trị của biến này sau khi

rót đầy trường này là bất kì cái gì bị bỏ lại sau khi loại

bỏ từ. Bạn sẽ thấy tại sao ngay sau đây.

Cho đến đây, điều này dường như không khác nhiều

lắm với cách thức trường văn bản làm việc - chúng ta chỉ

in ra vừa đủ trường (ngoại trừ rằng chúng ta vẫn tôn

trọng biên giới từ thay vì cắt bỏ nó theo chiều rộng từ).

Cái đẹp của trường được rót này xuất hiện khi bạn có

nhiều tham chiếu tới cùng biến theo cùng dạng thức.

Nhìn vào điều này:

format PEOPLE =

Name: @<<<<<<<<<<< Comment: ^<<<<<<<<<<<<<<<<<<

* Kí tự tách từ được định nghĩa bởi $:biến

Page 210: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

198

$name, $comment Comment: ^<<<<<<<<<<<<<<<<<< $comment Comment: ^<<<<<<<<<<<<<<<<<< $comment Comment: ^<<<<<<<<<<<<<<<<<< $comment .

Lưu ý rằng biến $comment xuất hiện bốn lần. Dòng

thứ nhất (dòng với trường tên) in ra tên người và vài từ

đầu của giá trị trong $comment. Nhưng trong tiến trình

tính dòng này, $comment bị thay đổi để cho các từ biến

mất. Dòng thứ hai lại tham chiếu đến cùng biến này

($comment), và do vậy sẽ lấy đi vài từ mới nữa từ cùng

biến này. Điều này cũng đúng cho dòng thứ ba và thứ tư.

Một cách có hiệu quả, điều tôi đã tạo ra là một hình chữ

nhật trong cái ra mà sẽ được rót đầy với các từ trong

$comment trải qua bốn dòng.

Điều gì xảy nếu toàn bộ văn bản chiếm ít hơn bốn

dòng? Được, bạn sẽ được một hay hai dòng trống. Điều

này có lẽ là được nếu bạn định in ra các nhãn và cần

đúng cùng số dòng cho mỗi mục sánh đúng với nhãn đó.

Nhưng nếu bạn định in ra một báo cáo, nhiều dòng trống

sẽ làm tốn giấy máy in của bạn.

Để giải quyết vấn đề này, chúng ta có thể dùng một

chỉ báo cắt bỏ. Bất kì dòng nào có chứa dấu ngã (~) đã bị

cắt bỏ (không in ra) nếu dòng chỉ có dấu cách. Bản thân

dấu ngã bao giờ cũng in ra như dấu trống, và có thể được

đặt ở bất kì đâu mà dấu cách có thể được đặt trong dòng.

Viết lại thí dụ vừa rồi:

format PEOPLE =

Name: @<<<<<<<<<<< Comment: ^<<<<<<<<<<<<<<<<<<

Page 211: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

199

$name, $comment ~ Comment: ^<<<<<<<<<<<<<<<<<< $comment ~ Comment: ^<<<<<<<<<<<<<<<<<< $comment ~ Comment: ^<<<<<<<<<<<<<<<<<< $comment .

Bây giờ, nếu lời bình luận chỉ chiếm hai dòng, các

dòng thứ ba và thứ tư tự động bị cắt bỏ.

Điều gì xảy ra nếu lời bình luận dài hơn bốn dòng?

Được, chúng ta có thể tạo 20 bản sao cho hai dòng cuối

của dạng thức đó, hi vọng rằng 20 dòng sẽ đủ cho nó.

Nhưng điều đó đi ngược với ý tưởng rằng Perl giúp bạn

lười biếng, cho nên có một cách lười biếng để thực hiện

điều đó. Bất kì dòng nào có chứa hai dấu ngã liên tiếp sẽ

được lặp lại một cách tự động cho tới khi kết quả là một

dòng trống hoàn toàn. (Dòng trống bị cắt bỏ.) Điều này

làm thay đổi dạng thức của chúng trông giống thế này:

format PEOPLE =

Name: @<<<<<<<<<<< Comment: ^<<<<<<<<<<<<<<<<<< $name, $comment ~~ Comment: ^<<<<<<<<<<<<<<<<<< $comment .

Cách này, nếu lời bình luận chiếm một, hai hay 20

dòng, chúng vẫn giải quyết ổn thoả.

Lưu ý rằng tiêu chuẩn để chấm dứt dòng lặp lại đòi

hỏi dòng phải trống tại điểm nào đó. Điều đó nghĩa là

bạn có lẽ không muốn bất kì văn bản hằng nào (khác hơn

dấu trống hay dấu ngã) trên dòng này, hay nếu không nó

sẽ chẳng bao giờ trở thành trống. (Có đấy, Perl sẽ lặp

Page 212: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

200

mãi trong trường hợp này. Điều này nghe đồn đã được

giải quyết trong Perl 5.0)

Dạng thức đầu trang

Nhiều báo cáo kết thúc trên một thiết bị in ra máy in

nào đó. Giấy máy in thông thường được cắt ra thành

chùm các trang, bởi vì phần lớn chúng đã thôi là cuộn

giấy từ lâu rồi. Cho nên văn bản được nạp vào máy in về

cơ bản phải tính tới biên giới trang để đưa vào các dòng

trống hay kí tự kéo trang để nhảy qua chỗ dư. Bây giờ

bạn có thể lấy một cái ra của chương trình Perl và nạp nó

vào một trình tiện ích nào đó (có thể thậm chí là được

viết trong Perl) mà làm việc phân trang này, nhưng vẫn

còn cách dễ hơn.

Perl cho phép bạn xác định dạng thức đầu trang để

cài bẫy xử lí trang. Perl đếm từng dòng được sinh ra và

gọi tới dạng thức cho một tước hiệu tệp đặc biệt. Khi

dạng thức cái ra tiếp không còn khít vào phần còn lại của

trang, Perl phun ra một dấu kéo trang tiếp sau đó tự động

gọi tới dạng thức đầu trang, và cuối cùng là in ra văn bản

theo dạng thức đã gọi. Theo cách đó, kết quả của một lần

gọi write sẽ không bao giờ cắt ngang qua biên giới trang

(tất nhiên trừ phi nó quá lớn mà không thể khít trên

chính bản thân trang).

Dạng thức đầu trang được xác định giống như các

dạng thức khác. Tên mặc định cho dạng thức đầu trang

đối với một tước hiệu tệp đặc biệt là giống như tên của

tước hiệu tệp có theo sau bởi _TOP (xin viết chữ hoa).

Perl định nghĩa biến $% là số dòng của dạng thức

Page 213: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

201

đầu trang mà đã được gọi cho một tước hiệu tệp đặc biệt,

cho nên bạn có thể dùng biến này trong dạng thức đầu

trang của mình để đánh số trang cho đúng. Chẳng hạn,

việc thêm định nghĩa dạng thức sau cho đoạn chương

trình trước ngăn cản các nhãn không bị xé lẻ qua biên

giới trang, và cũng đánh số trang liên tục:

format ADDRESSLABEL_TOP = My Address -- Page @< $% .

Chiều dài trang mặc định là 60 dòng. Bạn có thể

thay đổi điều này bằng việc đặt một biến đặc biệt, được

mô tả tóm tắt.

Perl không để ý liệu bạn có dùng print để in lên cùng

tước hiệu tệp hay không cho nên nó có thể ném đi số

dòng trên trang. Bạn có thể hoặc là viết lại chương trình

của mình để dùng các dạng thức để gửi đi mọi thứ, hay

tránh né biến “số dòng trên trang hiện tại” sau khi bạn

thực hiện lệnh print. Chút nữa chúng ta sẽ thấy cách thay

đổi giá trị này.

Thay đổi mặc định cho dạng thức

Tôi thường nói tới “mặc định” cho điều này điều nọ.

Được, Perl cung cấp một cách để vượt qua các mặc định

cho mọi bước. Ta nói về điều này.

Dùng select() để thay đổi tước hiệu tệp

Quay trở lại khi nói về print, trong chương 6, Cơ sở

Page 214: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

202

về vào/ra, tôi đã nói rằng print và print STDOUT là đồng

nhất, bởi vì STDOUT là mặc định cho print. Không hẳn

hoàn toàn thế. Mặc định thực cho print (và write cùng một

vài phép toán khác mà sẽ gặp ngay sau đây) là một khái

niệm kì cục được gọi là tước hiệu tệp hiện đang được

lựa.

Tước hiệu tệp hiện đang được lựa viết tắt là

STDOUT - để làm cho nó dễ in mọi thứ trên đầu ra

chuẩn. Tuy nhiên, bạn có thể thay thế tước hiệu tệp hiện

đang được lựa bằng toán tử select(). Toán tử này nhận

một tước hiệu tệp (hay biến vô hướng có chứa tên của

tước hiệu tệp) như một đối. Một khi tước hiệu tệp hiện

được lựa mà thay đổi, nó ảnh hưởng tới tất cả các phép

toán tương lai phụ thuộc vào tước hiệu tệp hiện được

lựa. Chẳng hạn:

print “hello world\n”; # giống như print STDOUT “hello world\n”;

select(LOGFILE) ; # chọn một tước hiệu tệp mới print “howdy, world\n”; giống như print LOGFILE “howdy

world\n”; print “more for the log\n”; # thêm về LOGFILE select (STDOUT); # chọn lại STDOUT print “back to stdout\n”; # lại trở về với đầu ra chuẩn

Lưu ý rằng phép toán select là khó tính - một khi bạn

đã lựa một tước hiệu mới, nó vẫn còn có hiệu quả cho tới

select tiếp.

Cho nên, một định nghĩa tốt hơn cho STDOUT vẫn

tôn trọng print và write là ở chỗ STDOUT là tước hiệu

hiện được lựa mặc định, hay tước hiệu “mặc định mặc

định”

Các chương trình con có thể thấy nhu cầu thay đổi

Page 215: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

203

tước hiệu tệp hiện được lựa. Tuy nhiên, sẽ bất ngờ nếu

bạn gọi một chương trình con rồi phát hiện ra là tất cả

các dòng văn bản bạn đã soạn công phu lại dồn thành

một bộ các bit nào đó bởi vì chương trình con đã thay

đổi tước hiệu tệp hiện được lựa mà không khôi phục lại

nó. Cho nên một chương trình con hành xử tốt cần phải

làm gì? Nếu chương trình con này biết rằng tước hiệu

hiện thời là STDOUT, chương trình con đó có thể khôi

phục tước hiệu đã lựa với chương trình tương tự như

trên. Tuy nhiên, điều gì xẩy ra nếu nơi gọi chương trình

con này đã thay đổi tước hiệu tệp đã lựa?

Vậy vấn đề trở thành giá trị cho lại từ select là một

xâu có chứa tên của tước hiệu đã lựa trước đó. Bạn có

thể nắm lấy giá trị này để khôi phục tước hiệu tệp đã lựa

trước đó, bằng việc dùng đoạn chương trình như thế này:

$oldhandle = select(LOGFILE) ; print “this goes to LOGFILE\n”; select($oldhandle); # khôi phục tước hiệu trước đó

Đấy, để làm thí dụ, dễ dàng hơn nhiều là chỉ cần đặt

LOGFILE một cách tường minh như một tước hiệu tệp

cho print, nhưng có một số thao tác đòi hỏi tước hiệu tệp

hiện được lựa phải thay đổi, như sẽ thấy ngay sau đây.

Thay đổi tên dạng thức

Tên dạng thức ngầm định cho một tước hiệu tệp là

giống như tước hiệu tệp. Tuy nhiên, bạn có thể cthay đổi

điều này cho tước hiệu tệp hiện được lựa bằng việc thiết

đặt tên dạng thức mới trong một biến đặc biệt được gọi

là $~. Bạn có thể cũng xem xét lại giá trị của biến này để

xem dạng thức hiện thời là gì đối với tước hiệu tệp hiện

Page 216: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

204

được lựa.

Chẳng hạn, để dùng dạng thức ADDRESSLABEL

trên STDOUT, cũng dễ như:

$_ = “ADDRESSLABEL” ;

Nhưng điều gì xảy ra nếu bạn muốn đặt dạng thức

cho tước hiệu tệp REPORT là SUMMARY? Chỉ cần vài

bước để làm điều đó ở đây:

$oldhandle = select (REPORT) ; $~ = “SUMMARY” ; select ($oldhandle) ;

Lần tiếp chúng ta nói:

write REPORT ;

thu được văn bản trên tước hiệu tệp REPORT,

nhưng dùng dạng thức SUMMARY.

Lưu ý rằng chúng ta đã cất giữ tước hiệu trước đó

vào biến vô hướng và rồi khôi phục lại nó sau này. Đây

là một thực hành lập trình tốt. Thực ra, khi viết chương

trình tôi có lẽ đã giải quyết sự tương tự điển hình trên

một dòng trước đó, và không giả thiết rằng STDOUT là

tước hiệu ngầm định.

Bằng việc đặt dạng thức hiện thời cho một tước hiệu

tệp đặc biệt, bạn có thể xen lẫn nhiều dạng thức khác

nhau trong một báo cáo.

Đổi tên dạng thức đầu trang

Cũng như chúng ta có thể thay đổi tên của dạng thức

cho một tước hiệu tệp đặc biệt bằng việc đặt biến $~,

Page 217: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

205

chúng ta cũng có thể thay đổi dạng thức đầu trang bằng

việc đặt biến $^. Biến này giữ tên của dạng thức đầu

trang cho tước hiệu tệp hiện đang được lựa và là đọc/ghi,

có nghĩa là bạn có thể kiểm tra lại giá trị của nó để xem

tên dạng thức hiện thời và bạn có thể thay đổi nó qua

việc gán cho nó.

Đổi chiều dài trang

Nếu dạng thức đầu trang đã được xác định, chiều dài

trang trở thành quan trọng. Theo mặc định, chiều dài

trang là 60 dòng - tức là, khi lệnh write khớp với cuối

dòng 60, dạng thức đầu trang sẽ được tự động gọi tới

trước khi in văn bản.

Đôi khi 60 dòng lại không khớp. Bạn có thể thay đổi

điều này bằng việc đặt biến $=. Biến này giữ chiều dài

trang hiện thời cho tước hiệu tệp đang được lựa. Một lần

nữa, để thay đổi nó sang một tước hiệu tệp khác hơn

STDOUT (tước hiệu tệp hiện đang được lựa ngầm định),

bạn sẽ cần dùng toán tử select(). Sau đây là cách thay đổi

tước hiệu tệp LOGFILE để có trang 30 dòng:

$old = select(LOGFILE) ; # lựa LOGFILE và cất

giữ tước hiệu cũ

$= = 30 ;

select($old) ;

Việc thay đổi chiều dài trang sẽ không có hiệu lực

cho tới lần tiếp gọi tới dạng thức đầu trang. Nếu bạn đặt

nó trước khi bất kì văn bản nào được đưa ra qua tước

hiệu tệp này qua một dạng thức, nó sẽ làm việc tốt vì

Page 218: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

206

dạng thức đầu trang được gọi ngay lập tức tại văn bản

đầu tiên được dạng thức.

Thay đổi vị trí trên trang

Nếu bạn dùng print để in một văn bản của mình lên

một tước hiệu tệp, nó sẽ trộn lẫn số đếm dòng vị trí trang

vì Perl không đếm số dòng qua bất kì cái gì khác ngoài

write. Nếu bạn muốn để cho Perl biết rằng bạn đang đưa

ra một vài dòng phụ, bạn có thể điều chỉnh số dòng được

đếm bởi việc thay đổi biến $-. Biến này chứa số dòng

còn lại trên trang trên tước hiệu tệp hiện được lựa. Mỗi

write sẽ làm giảm giá trị của số dòng còn lại theo số dòng

thực tế đưa ra; khi số đếm này đạt tới không, dạng thức

đầu trang sẽ được gọi tới và giá trị của $- được sao từ

biến $= (chiều dài trang).

Chẳng hạn, để bảo Perl rằng bạn đã gửi một dòng

phụ ra STDOUT, làm điều như thế này:

write ; # gọi dạng thức STDOUT trên STDOUT ... ; print “Một dòng phụ ... đây rồi!\n” ; # dòng này đi ra

STDOUT $- -- ; # giảm $- để chỉ ra dòng không ghi đã gửi tới

STDOUT ... ; write ; # điều này vẫn làm việc, tính cả dòng phụ

Tại đầu chương trình, $- được đặt là không cho mỗi

tước hiệu tệp. Điều này đảm bảo rằng dạng thức đầu

trang sẽ là cái đầu tiên được gọi tới cho từng tước hiệu

tệp tuỳ theo write đầu tiên.

Page 219: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

207

Bài tập

1. Viết một chương trình để mở tệp /etc/passwd và in ra

tên người dùng, (số hiệu) ID người dùng, và tên thực

theo cột có dạng thức. Dùng format và write.

2. Thêm dạng thức đầu trang vào chương trình trước.

(Nếu tệp mật hiệu còn ít, bạn có thể cần đặt chiều dài

trang thành số nào đó như 10 dòng chẳng hạn để cho

bạn có thể thu được nhiều thể nghiệm của đầu trang.)

3. Thêm số trang tăng tuần tự vào đầu trang, để cho bạn

thu được trang 1, trang 2 vân vân trên cái ra.

Page 220: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

208

Page 221: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

209

12

Truy nhập

danh mục

Chuyển vòng quanh cây danh mục

Cho tới nay, có lẽ bạn đã quen thuộc với khái niệm

về danh mục hiện thời và dùng chỉ lệnh cd của vỏ. Trong

lập trình UNIX, bạn vẫn gọi lời gọi hệ thống chdir() để

thay đổi danh mục hiện thời của tiến trình, và đây là tên

mà Perl cũng dùng.

Toán tử chdir() trong Perl nhận một đối - một biểu

thức tính ra một tên danh mục để sẽ đặt làm danh mục

hiện thời. Như với hầu hết các toán tử khác, chdir() cho

lại đúng khi bạn đã đổi được sang danh mục yêu cầu và

cho lại sai nếu bạn không thể làm được điều này. Sau

Trong chương này:

Đi quanh cây danh mục

Globbing

Tước hiệu danh mục

Mở và đóng tước hiệu danh mục

. Đọc tước hiệu danh mục

Page 222: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

210

đây là một thí dụ:

chdir(“/etc”) || die “không thể chuyển sang /etc (kì thật!)” ;

Các dấu ngoặc tròn là tuỳ chọn, cho nên bạn cũng có

thể viết cách khác bằng chương trình như:

print “Bạn muốn đi đâu?” ; chop ($where) { if (chdir $where) { # tới đó } else { # không tới đó }

Bạn không thể tìm ra được bạn ở đâu mà không đưa

ra chỉ lệnh pwd. Chúng ta sẽ học về việc đưa ra các chỉ

lệnh trong Chương 14, Quản lí tiến trình.

Mọi tiến trình UNIX đã có danh mục riêng của nó.

Khi một tiến trình mới được đưa ra, nó kế thừa danh mục

hiện thời của cha mẹ, nhưng đó là chấm hết mối ghép

nối. Nếu chương trình Perl của bạn đổi danh mục của nó,

điều ấy sẽ ảnh hưởng tới lớp vỏ (hay bất kì cái gì) đã

khởi động tiến trình Perl. Giống vậy, các tiến trình mà

Perl tạo ra không thể nào ảnh hưởng tới danh mục hiện

thời của chương trình Perl. Các danh mục hiện thời cho

những tiến trình mới này được kế thừa từ danh mục hiện

thời của chương trình Perl.

Globbing

Lớp vỏ thường nhận đối dòng lệnh có dấu sao riêng

biệt (*) và chuyển nó thành một danh sách tất cả các tên

tệp trong danh mục hiện thời. Vậy, khi bạn nói rm *, bạn

Page 223: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

211

sẽ loại bỏ đi tất cả các tệp khỏi danh mục hiện thời. (Chớ

thử điều này nếu bạn không muốn chọc tức người quản

trị hệ thống của mình khi bạn yêu cầu các tệp đó phải

được cất giữ.) Tương tự, [a-m]*.c xem như đối dòng lệnh

sẽ biến thành một danh sách tất cả các tên tệp trong danh

mục hiện thời mà có chữ bắt đầu thuộc vào nửa trước

của bảng chữ cái, và kết thúc với .c, và /etc/host* là danh

sách tất cả các tên tệp bắt đầu với host trong danh mục

/etc. (nếu điều này là mới đối với bạn, có lẽ bạn muốn

đọc thêm nhiều nữa về kịch đoạn lớp vỏ ở đâu đó khác

trước khi xử lí tiếp.)

Việc mở rộng đối dòng lệnh như * hay /etc/host*

thành một danh sách các tên tệp sánh đúng được gọi là

globbing. Perl hỗ trợ cho globbing qua một cơ chế rất

đơn giản - chỉ đặt mẫu globbing vào giữa hai ngoặc

nhọn, như:

@a = < /etc/host*> ;

Trong hoàn cảnh mảng, như trình bầy ở đây, glob

cho lại một danh sách tất cả các tên sánh đúng với mẫu

(cũng như lớp vỏ đã mở rộng các đối glob), hay một

danh sách rỗng nếu không sánh đúng. Trong hoàn cảnh

vô hướng, sẽ cho lại tên tiếp đó sánh đúng, hay undef nếu

không còn tên nào sánh đúng: điều này rất giống với việc

đọc từ một tước hiệu tệp. Chẳng hạn, mỗi lúc nhìn vào

một tên:

while ($nextname = </etc/host*> ) { print “một trong các tệp là $nextname\n” ; }

Tại đây tên tệp được cho lại bắt đầu với /etc/host,

cho nên nếu bạn muốn chỉ phần cuối cùng của tên, bạn

Page 224: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

212

sẽ phải tự mình đẽo gọt nó, giống như:

while ($nextname = </etc/host*> ) {

$nextname =~ s#.* / ## ; # bỏ phần trước dấu sổ chéo

print “một trong các tệp là $nextname\n” ; }

Nhiều khuôn mẫu được phép đặt vào bên trong đối

glob - các danh sách được xây dựng tách biệt và rồi được

nối lại dường như chúng là một danh sách lớn:

@fred_barney_files = <fred* barney*> ;

Nói cách khác, glob cho lại cùng các giá trị tương

đương với chỉ lệnh echo với cùng các tham biến sẽ cho

lại* .

Mặc dầu globbing và hàm sánh biểu thức chính qui

là tương tự nhau, ý nghĩa của các kí tự đặc biệt ở đây lại

hoàn toàn khác nhau. Bạn đừng lẫn lộn giữa hai cách kí

hiệu này, nếu không bạn sẽ khó hiểu tại sao <\.c$> lại

không tìm thấy tất cả các tệp có tận cùng là .c!

Đối cho glob là một biến được chen vào trước khi

mở rộng. Bạn có thể có các tham chiếu biến Perl để lựa

ra một dấu đại diện dựa trên một xâu được tính vào lúc

chạy:

if (-d “/usr/etc”) { $where = “/usr/etc” ; } else { $where = “/etc” ; }

* Điều này thực tại không đáng ngạc nhiên khi bạn hiểu rằng để thực

hiện glob, Perl đơn thuần bắn ra lớp vỏ C để lấy danh sách đặc biệt

rồi phân tích cái nó nhận được.

Page 225: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

213

@files = <$where/*> ;

Tại đây tôi đặt $where là một trong hai tên danh mục

khác, dựa trên liệu danh mục /usr/etc có tồn tại hay

không. Rồi tôi lấy một danh sách các tệp trong danh mục

đã được chọn. Lưu ý rằng biến $where được mở rộng, có

nghĩa là khuôn mẫu được glob hoặc là /etc/* hay /usr/etc/*.

Có một ngoại lệ cho qui tắc này: khuôn mẫu <$var>

(nghĩa là dùng biến $var như một khuôn mẫu glob toàn

bộ) phải được viết là <${var}> bởi lí do tôi không muốn

đưa vào tại điểm này**

.

Tước hiệu danh mục

Nếu bạn muốn có hương vị UNIX đặc biệt như hàm

thư viện readdir, Perl cũng cung cấp việc truy nhập vào

trình con đó (và phép so sánh của nó) bằng việc dùng

tước hiệu danh mục. Tước hiệu danh mục là một tên lấy

từ một không gian tên khác, và những thận trọng cùng

lời khuyên áp dụng cho tước hiệu tệp, cũng áp dụng cho

tước hiệu danh mục (bạn không thể dùng một từ dành

riêng, và chữ hoa được khuyên nên dùng). Tước hiệu tệp

FRED và tước hiệu danh mục FRED là không có quan

hệ.

Tước hiệu danh mục biểu thị cho một ghép nối tới

một danh mục đặc biệt. Thay vì đọc dữ liệu (như từ tước

**

Kết cấu <$fred> đọc một dòng từ tước hiệu tệp có tên theo nội

dung của biến vô hướng $fred. Cùng với một số tính năng khác

không được nói tới trong cuốn sách này, kết cấu này cho phép bạn

dùng “tước hiệu tệp gián tiếp”, chỗ mà tên của tước hiệu được

truyền quanh và thao tác dường như là một dữ liệu

Page 226: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

214

hiệu tệp), bạn dùng tước hiệu danh mục để đọc một danh

sách các tên tệp bên trong danh mục đó. Tước hiệu danh

mục bao giờ cũng được mở chỉ đọc - bạn không thể nào

dùng một tước hiệu danh mục để thay đổi tên của tệp hay

xoá tệp.

Nếu thư viện của bạn không cung cấp readdir (và

bạn không cung cấp một trong trong những cài đặt thay

thế sao chép được trong khi xây dựng Perl), việc dùng

bất kì một trong những trình con này sẽ là lỗi định mệnh,

và chương trình của bạn sẽ không qua được trình biên

dịch - nó sẽ bị bỏ trước khi dòng chương trình đầu tiên

được thực hiện. Perl cố gắng rất vất vả để cô lập bạn với

môi trường, nhưng nó không phải là một công nhân mầu

nhiệm.

Mở và đóng tước hiệu danh mục

Hàm opendir() làm việc giống như lời gọi thư viện

của cùng tên. Bạn trao cho nó tên của một tước hiệu

danh mục mới và một giá trị xâu kí hiệu cho tên này của

danh mục sẽ được mở ra. Giá trị cho lại từ opendir() là

đúng nếu danh mục có thể được mở, và là sai trong các

trường hợp khác. Sau đây là một thí dụ:

opendir(ETC, “/etc”) || die “Không thể mở opendir /etc” ;

Thông thường, tại điểm này, chúng ta đang chơi với

tước hiệu danh mục ETC, nhưng có thể hay hơn nếu biết

cách đóng tước hiệu danh mục này trước. Điều này được

thực hiện với closedir(), theo cách tương tự như việc dùng

close(), giống như:

closedir(ETC) ;

Page 227: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

215

Giống như close(), closedir() thường cũng chẳng cần,

vì tất cả các tước hiệu danh mục đã tự động được đóng

lại trước khi chúng được mở lại hay tại cuối chương

trình.

Đọc một tước hiệu danh mục

Một khi chúng ta có việc mở một tước hiệu danh

mục, chúng ta có thể đọc danh sách các tên bằng

readdir(), nhận một tham biến: tên của tước hiệu danh

mục. Mỗi lần gọi tới readdir() trong hoàn cảnh vô hướng

đã cho lại tên tệp tiếp (hệt như basename - bạn sẽ không

bao giờ nhận được bất kì dấu sổ chéo nào trong giá trị

cho lại) theo một trật tự dường như ngẫu nhiên*. Nếu

không còn tên nào nữa, readdir() cho lại undef. Việc gọi

readdir() trong hoàn cảnh mảng cho lại tất cả các tên còn

lại như một danh sách với mỗi tên cho một phần tử. Sau

đây là một thí dụ về danh sách tất cả các tên lấy từ danh

mục /etc:

opendir(ETC, “/etc”) || die “Không có etc?” ; while ($name = readdir(ETC)) { # hoàn cảnh vô hướng print “$name\n” ; # in ., .., passwd, group vân vân } closedir(ETC) ;

Và sau đây là cách lấy chúng tất cả theo trình tự

bảng chữ với sự hỗ trợ của sort:

opendir(ETC, “/etc”) || die “Không có etc?” ; foreach ($name (sort readdir(ETC)) { # hoàn cảnh vô

* Nói riêng, đây là trật tự mà các tên tệp được cất giữ trong danh

mục - cùng trật tự chưa sắp mà bạn nhận được từ chỉ lệnh find hay

ls -f.

Page 228: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

216

hướng, có sắp xếp print “$name\n” ; # in ., .., passwd, group vân vân } closedir(ETC) ;

Lưư ý rằng các tên bao gồm các tệp bắt đầu với một

dấu chấm. Điều này không giống như glob với <*> mà

không cho lại các tên bắt đầu với một chấm (giống như

echo * của lớp vỏ ).

Bài tập

1. Viết một chương trình để thay đổi danh mục cho một

vị trí được xác định như cái vào, rồi liệt kê các tên

của các tệp này theo trình tự abc sau khi thay đổi ở

đó. (Đừng hiện danh sách nếu việc đổi danh mục

không thành công - đơn giản chỉ cảnh báo cho đọc

giả.)

2. Sửa đổi chương trình này để bao quát tất cả các tệp,

không chỉ những tệp không bắt đầu với dấu chấm.

Thử làm điều này với cả một glob và một tước hiệu

danh mục.

Page 229: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

217

13

Thao tác tệp

và danh mục

Loại bỏ tệp

Trước đây, bạn đã biết cách tạo ra một tệp từ bên

trong Perl bằng việc mở nó làm cái ra thông qua một

tước hiệu tệp. Bây giờ, chúng ta sẽ gặp nguy hiểm hơn,

và học cách loại bỏ một tệp (rất thích hợp với Chương

13, bạn có nghĩ như vậy không?)

Toán tử unlink() của Perl (được lấy theo tên của lời

gọi hệ thống UNIX) sẽ xoá đi một tên đối với một tệp

(mà có thể mang nhiều tên). Khi tên cuối cùng cho một

tệp bị xoá đi, bản thân tệp này cũng sẽ bị loại bỏ đi. Điều

này đích xác là là điều chỉ lệnh rm đã thực hiện. Bởi vì

một tệp thường chỉ có một tên (trừ phi bạn đã tạo ra móc

nối cứng), bạn có thể nghĩ đến việc loại bỏ một tên như

việc loại bỏ tệp đối với phần lớn các trường hợp. Giả sử

Trong chương này:

. Loại bỏ tệp

. Đổi tên tệp

. Tạo ra tên khác cho

tệp (móc nối)

. Tạo và loại bỏ danh

mục

.Sửa đổi quyền thao tác

. Sửa đổi quyền sở hữu

. Sửa đổi thời gian

Page 230: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

218

như thế, sau đây là cách loại bỏ một tệp có tên là fred và

rồi loại bỏ một tệp được xác định trong khi thực hiện

chương trình:

unlink(“fred”) ; # nói lời tạm biệt với fred print “Bạn muốn xoá bỏ tệp nào?” ; chop ($name = <STDIN>) ; unlink ($name) ;

Toán tử unlink có thể nhận một danh sách các tên cần

phải tháo móc nối như:

unlink (“spottedowl”, “meadowlark”) ; # giƠt chết hai con chim

unlink (<*.o>) ; # hệt như “rm *.o” trong lớp vỏ

Lưu ý rằng glob được tính toán theo hoàn cảnh

mảng, tạo ra một danh sách các tên tệp sánh đúng với

khuôn mẫu, và điều này đích thị là điều cần để nạp vào

unlink().

Giá trị cho lại của unlink() là số các tệp đã bị ảnh

hưởng thành công. Nếu có một đối tên tệp và nó bị xoá

đi, kết quả là một, ngoài ra, nó là không. Nếu có ba tên

tệp nhưng chỉ có hai tên có thể bị xoá đi, kết quả là hai.

Lưu ý rằng bạn không thể biết được hai tệp nào, cho nên

nếu bạn cần phải hình dung ra việc xoá nào bị hỏng, bạn

phải thực hiện lần lượt từng việc xoá một. Sau đây là

cách xoá đi tất cả các tệp đích (tận cùng bởi o.) trong khi

báo cáo lại lỗi với bất kì tệp nào mà không thể nào xoá

được.

foreach $file (<*.o>) { # bước qua danh sách các tệp .o unlink ($file) || print “gặp rắc rối với tệp $file\n” ; }

Nếu unlink cho lại 1 (có nghĩa là một tệp xác định

quả thực đã bị xoá), kết quả đúng sẽ nhảy qua câu lệnh

Page 231: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

219

print. Nếu tệp này không thể nào bị xoá đi, kết quả 0 là

sai, cho nên câu lệnh print được thực hiện. Lại một lần

nữa, điều này có thể được đọc một cách tuỳ ý như “tháo

móc nối tệp này hay cho tôi biết về nó.”

Nếu toán tử unlink được nêu ra không có đối, biến $_

lần nữa lại được dùng như mặc định. Vậy, chúng ta có

thể viết chu trình trên là:

foreach (<*.o>) { # bước qua danh sách các tệp .o unlink || print “gặp rắc rối khi xoá $_\n” ; }

Đổi tên tệp

Trong lớp vỏ, bạn đổi tên tệp bằng chỉ lệnh mv. Với

Perl, cùng phép toán này được kí hiệu bằng rename($old,

$new). Sau đây là cách thay đổi tệp có tên fred thành

barney:

rename(“fred”, “barney”) || die “Không thể đổi tên fred thành barney”;

Giống như hầu hết các toán tử khác, toán tử rename()

cho lại giá trị đúng nếu công việc diễn ra thành công,

cho nên ở đây tôi đã kiểm tra kết quả này để xem liệu

việc đổi tên có thực sự xảy ra không.

Chỉ lệnh mv thực hiện một chút ít ảo thuật hậu cảnh

để tạo ra một đường dẫn đầy đủ khi bạn nói mv file

some-directory. Tuy nhiên, toán tử rename(), lại không

thể làm điều đó. Phép toán Perl tương đương là:

rename(“file”, “some-directory/file”) ;

Lưư ý rằng trong Perl chúng ta phải nói tên của tệp

Page 232: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

220

bên trong danh mục mới một cách tường minh. Cũng

vậy, chỉ lệnh mv sao tệp này khi tệp được đổi tên từ thiết

bị này sang thiết bị khác (nếu bạn có một trong các cài

đặt UNIX tốt hơn). Toán tử rename() không thật khôn

cho lắm, cho nên bạn sẽ nhận được lỗi, chỉ ra bạn phải

chuyển vòng nó theo cách khác (có lẽ bởi việc gọi chỉ

lệnh mv theo cùng tên).

Tạo ra tên thay phiên cho một tệp (móc nối)

Dường như là một tên cho một tệp vẫn không đủ,

đôi khi bạn muốn có hai tên, ba tên hay cả tá tên cho

cùng một tệp. Phép toán tạo ra nhiều tên thay phiên cho

một tệp được gọi là móc nối. Hai dạng chính của móc

nối là móc nối cứng và móc nối tượng trưng (cũng còn

được gọi là móc nối mềm).

Về móc nối cứng và mềm

Móc nối cứng với một tệp là không thể nào phân

biệt được với tên gốc cho tệp này - không có móc nối

đặc biệt nào nhiều hơn là tên thật cho tệp này so với bất

kì tên khác.

Lõi UNIX giữ dấu vết số các móc nối cứng tham

chiếu tới một tệp vào bất kì lúc nào. Khi một tệp lần đầu

tiên được tạo ra, nó bắt đầu với một liên kết. Mỗi liên kết

cứng mới lại làm tăng thêm số đếm này. Mỗi khi liên kết

bị loại bỏ, số đếm lại được giảm đi. Khi liên kết cuối

cùng với một tệp biến mất, tệp đó cũng mất theo.

Mọi móc nối cứng với một tệp phải nằm ở cùng một

Page 233: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

221

đơn vị lưu thông tin (thông thường là một đĩa hay một

phần của đĩa). Bởi điều này, bạn không thể nào tạo ra

một móc nối cứng mới với một tệp nằm trên một đơn vị

lưu thông tin khác.

Móc nối cứng cũng bị hạn chế vào các danh mục.

Để giữ cho cây các tệp của UNIX còn là một cây thay vì

là một mớ hỗn độn bất kì, một danh mục mỗi lần chỉ

được phép mang một tên từ gốc, một móc nối từ tệp

chấm bên trong nó, và một chùm các móc nối cứng chấm

chấm từ từng danh mục con của nó. Nếu bạn thử tạo ra

một móc nối cứng khác cho một danh mục, bạn sẽ nhận

được một lỗi (chừng nào mà bạn còn chưa là siêu người

dùng).

Một móc nối tượng trưng là một loại tệp đặc biệt có

chứa đường dẫn như dữ liệu. Khi tệp này được mở, lõi

UNIX sẽ nhìn vào nội dung của nó xem như các kí tự

phụ cho đường dẫn, làm cho lõi phải dò qua cây danh

mục thêm nữa, bắt đầu với tên mới.

Chẳng hạn, nếu một móc nối tượng trưng có tên fred

lại chứa tên barney, việc mở fred thực sự là một chỉ báo

để mở barney. Nếu barney là một danh mục, fred/wilma

sẽ thay vì thế mà tham chiếu tới barney/wilma.

Nội dung của móc nối tượng trưng (nơi móc nối

tượng trưng trỏ tới) không phải tham chiếu tới một tệp

hay danh mục đã có. Khi fred được tạo ra, barney thậm

chí phải không được tồn tại - thực ra, nó có thể chẳng

bao giờ tồn tại cả! Nội dung của một móc nối tượng

trưng có thể tham chiếu tới một đường dẫn mà sẽ đưa

bạn tới nơi cất giữ hiện tại, cho nên bạn có thể tạo ra một

móc nối tượng trưng tới một tệp trên một thiết bị lưu trữ

Page 234: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

222

khác.

Trong khi đi theo tên mới, lõi có thể chạy qua một

móc nối tượng trưng khác. Móc nối tượng trưng này

thậm chí còn cho phần mới hơn so với đường dẫn phải

theo. Thực ra, móc nối tượng trưng có thể trỏ tới các

móc nối tượng trưng khác, mà thông thường có ít nhất

tám mức móc nối tượng trưng được phép, mặc dầu điều

này hiếm khi được dùng trong thực hành.

Móc nối cứng bảo vệ cho nội dung của tệp khỏi bị

mất (vì nó vẫn còn đếm khi có một trong các tên của

tệp). Móc nối tượng trưng không thể nào giữ được nội

dung khỏi bị mất. Một móc nối tượng trưng có thể xuyên

qua các thiết bị lưu trữ hiện có trong khi móc nối cứng,

lại không thể thế được. Chỉ móc nối tượng trưng mới có

thể được làm thành một danh mục.

Bạn phải có khả năng ghi lên danh mục nơi bạn

đang tạo ra ra hoặc là một loại móc nối, cho dù bạn có

thể không cần phải có khả năng mở tệp mà đang móc nối

tới. Bạn phải có khả năng thống kê stat cho tệp (như có

thể nói bởi ls -l filename) để tạo ra một móc nối cứng,

nhưng bạn không cần một truy nhập như vậy xem như

một móc nối tượng trưng.

Tạo ra các móc nối cứng và mềm bằng Perl

Chỉ lệnh ln của UNIX tạo ra móc nối cứng. Chỉ lệnh

ln fred bigdumbguy

tạo ra một móc nối cứng từ tệp fred (mà phải tồn tại)

đối với bigdumbguy. Trong Perl điều này được diễn tả là:

Page 235: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

223

link (“fred”, “bigdumbguy”) || die “không thể móc nối fred với bigdumbguy” ;

Toán tử link() nhận hai đối, tên tệp cũ và biệt hiệu

mới cho tệp đó. Toán tử này cho lại đúng nếu việc móc

nối thành công. Như với chỉ lệnh mv, chỉ lệnh ln của

UNIX thực hiện vài thủ thuật đằng sau hậu trường, cho

phép bạn xác định danh mục đích đối với biệt hiệu mới

mà không đặt tên tệp bên trong danh mục này. Toán tử

link() (giống như toán tử rename()) cũng không thật thông

minh cho lắm, và bạn phải xác định tên tệp hoàn toàn

tường minh.

Với một móc nối cứng, tên tệp cũ không thể là một

danh mục, còn biệt hiệu mới lại phải là trên cùng hệ

thống tệp. (Hạn chế này là một phần của lí do rằng các

móc nối tượng trưng được tạo ra.)

Trên hệ thống có hỗ trợ cho móc nối tượng trưng,

chỉ lệnh ln của UNIX có thể được cho thêm tuỳ chọn -s

để tạo ra móc nối tượng trưng. Cho nên, để tạo ra một

móc nối tượng trưng từ barney sang neighbor (để cho

tham chiếu tới neighbor thực tại là một tham chiếu tới

barney), bạn phải dùng cái gì đó tựa như thế này:

ln -s barney neighbor

và trong Perl, bạn nên dùng toán tử symlink(), tựa

như

symlink(“barney”, “neighbor”) || die “Không thể tạo móc nối tượng trưng sang

neighbor” ;

Lưu ý rằng barney không cần tồn tại (Betty đáng

thương ơi!), cả bây giờ hay trong tương lai. Trong

trường hợp này, một tham chiếu tới neighbor sẽ cho lại

Page 236: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

224

cái gì đó mơ hồ tựa như file not found.

Khi bạn gọi ls -l trên danh mục có chứa một móc nối

tượng trưng, bạn nhận được một chỉ báo về cả hai tên

của móc nối tượng trưng và nơi móc nối trỏ tới. Perl cho

bạn cùng thông tin này qua toán tử readlink(), làm việc

đáng ngạc nhiên giống như lời gọi hệ thống với cùng tên,

cho lại một tên được trỏ tới bởi một móc nối tượng trưng

đặc biệt. Cho nên, phép toán này:

if ($x = readlink(“neighbor”)) { print “neighbor trở tới ‘$x’\n” ; }

nên nói về barney nếu tất cả đã ổn thoả. Nếu móc

nối tượng trưng không tồn tại hay không thể được đọc

hay thậm chí không phải là móc nối tượng trưng,

readlink() cho lại undef (sai tất định), mà đấy là lí do tại

sao tôi lại đang thử nó ở đây.

Trên hệ thống không có móc nối tượng trưng, cả hai

toán tử symlink() và readlink() đã không được dịch, làm

cho chương trình bị bỏ trước khi nó được bắt đầu. Điều

này là vì không có sự tương đương sánh được nào cho

móc nối tượng trưng trên các hệ thống không hỗ trợ cho

nó. Perl có thể che dấu một số tính năng phụ thuộc hệ

thống với bạn, nhưng một số tính năng sẽ dò rỉ ra ngay.

Đây là một trong chúng.

Tạo ra và xoá danh mục

Có lẽ bạn không thể làm được điều này thêm nếu

không biết về chỉ lệnh mkdir của UNIX, chỉ lệnh tạo ra

danh mục chứa các tệp khác, và các danh mục khác.

Page 237: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

225

Thành phần tương đương của Perl là toán tử mkdir(),

nhận một tên cho một danh mục mới và một mốt mà sẽ

ảnh hưởng tới quyền về danh mục được tạo ra. Mốt này

được xác định như một số mà được diễn giải theo dạng

thức quyền bên trong. Nếu bạn chưa quen thuộc với các

quyền bên trong này, xem chmod(2) trong tài liệu. Nếu

bạn vội vã, chỉ cần dùng 0777 cho mốt này và mọi thứ sẽ

gần như làm việc cả. Sau đây là một thí dụ về cách tạo ra

một danh mục có tên gravelpit:

mkdir (“gravelpit”, 0777) || die “không thể mkdir gravelpit” ;

Chỉ lệnh rmdir của UNIX loại bỏ đi danh mục rỗng -

bạn sẽ thấy thành phần Perl tương đương với cùng tên.

Sau đây là cách làm cho Fred thành thất nghiệp:

rmdir(“gravelpit”) || die “không thể rmdir gravelpit” ;

Mặc dầu những toán tử Perl này lợi dụng cùng tên

lời gọi hệ thống, chúng ta vẫn làm việc trên các hệ thống

không có những lời gọi này (mặc dầu có chậm hơn đôi

chút). Perl tự động gọi tới các tiện ích /bin/mkdir và

/bin/rmdir cho bạn (hay bất kì cái gì chúng được gọi trên

hệ thống của bạn). Cũng là giúp một phần nhân danh

tính khả chuyển.

Thay đổi phép sử dụng

Phép sử dụng đối với một tệp hay danh mục xác

định ra ai (theo phạm trù rộng) có thể làm gì (nhiều hay

ít) đối với tệp hay danh mục đó. Dưới UNIX, cách điển

hình để thay đổi phép trên tệp là dùng chỉ lệnh chmod

(xem tài liệu nếu bạn chưa quen thuộc với sự vận hành

Page 238: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

226

của nó). Tương tự như vậy, Perl thay đổi phép qua toán

tử chmode(). Toán tử này nhận một mốt số và một danh

sách các tên tệp, và cố gắng thay đổi phép của tất cả các

tên tệp sang theo mốt đã chỉ ra. Để làm cho các tệp fred

và barney cả hai đã là đọc/ghi với mọi người, chẳng hạn,

làm điều gì đó tựa như thế này:

chmod(0666, “fred”, “barney”) ;

Tại đây, giá trị của 0666 cho phép đọc/ghi đối với

người dùng, nhóm và người khác, cho chúng phép mong

muốn.

Giá trị cho lại của chmod() là số các tệp được điều

chỉnh thành công (cho dù nếu việc điều chỉnh chẳng làm

gì cả), cho nên nó làm việc giống như unlink(), và bạn nên

xử lí nó như xử lí kiểm tra lỗi. Sau đây là cách thay đổi

phép cho fred và barney trong khi kiểm tra lỗi cho từng

tệp:

foreach $file (“fred”, “barney”) { unless (chmod(0666, $file) { print “Hơ ... không thể đổi được chmod

$file.’n” ; } }

Thay đổi quyền sở hữu

Mọi tệp (hay danh mục, hay lối vào thiết bị, hay bất

kì cái gì) trong hệ thống tệp đã có một người chủ và một

nhóm. Người chủ và nhóm của một tệp xác định ra ai là

người được phép tiến hành (đọc, ghi và/hoặc thực hiện

tệp). Người chủ và nhóm của một tệp được xác định vào

lúc tệp được tạo ra, nhưng trong hoàn cảnh nào đó, bạn

Page 239: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

227

có thể thay đổi được chúng. (Hoàn cảnh đúng đắn tuỳ

thuộc vào dạng UNIX đặc biệt mà bạn đang chạy - xin

xem chi tiết tài liệu về chwn.)

Toán tử chown() nhận một số hiệu người dùng ID

(UID), số hiệu nhóm ID (GID), và một danh sách các tên

tệp, dự định thay đổi quyền sở hữu cho từng tệp được

liệt kê như đã xác định. Việc thay đổi thành công được

chỉ ra bằng giá trị cho lại khác không, giá trị bằng số

lượng các tệp đã thay đổi thành công - giống hệt chmod()

hay unlink(). Lưu ý rằng bạn đang thay đổi cả người chủ

và nhóm một lúc; bạn không thể thay đổi chỉ một thành

phần. Cũng cần chú ý rằng bạn phải dùng UIS và GID

số, không phải là tên tượng trưng tương ứng (mặc dầu

chỉ lệnh chmod chấp nhận tên). Chẳng hạn, nếu fred là

UID 1234 còn nhóm mặc định stoners của fred là GID

35, thế, chỉ lệnh sau đây tạo ra tệp slate và granite thuộc

vào fred và nhóm mặc định cho anh ta:

chown (1234, 35, “slate”, “granite”) ; # hệt như: # chown fred slate granite # chgrp stoners slate granite

Trong chương sau, bạn sẽ học cách chuyển fred sang

1234 và stoners sang 35.

Thay đổi nhãn thời gian

Liên kết với từng tệp là một tập ba nhãn thời gian.

Các nhãn thời gian này đã được đề cập ngắn gọn tới khi

chúng nói về việc lấy thông tin về tệp: lần truy nhập cuối

cùng, thời gian sửa đổi cuối cùng, và thời gian thay đổi

inode. Hai nhãn thời gian đầu có thể được đặt cho giá trị

tuỳ ý bằng toán tử utime() (mà tương ứng trực tiếp với lời

Page 240: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

228

gọi hệ thống UNIX cùng tên). Việc đặt hai giá trị này sẽ

tự động đặt lại giá trị thứ ba thành thời gian hiện tại, cho

nên không có vấn đề gì liên quan tới việc đặt giá trị thứ

ba.

Các giá trị được đo theo thời gian nội bộ của UNIX,

có nghĩa là một số nguyên chỉ ra số giây đã trôi qua từ

nửa đêm GMT, 1 tháng 1 năm 1970 - một con số đạt tới

quãng bẩy trăm triệu, khi cuốn sách này đang được viết

ra. (Bên trong, nó được biểu diễn như một số 32-bit có

dấu, và đôi khi sẽ bị tràn sớm trong thế kỉ tới.)

Toán tử utime() làm việc giống như chmod() và

unlink(). Nó nhận một danh sách các tên tệp và cho lại số

các tệp bị ảnh hưởng. Sau đây là cách làm cho các tệp

fred và barney trông giống như chúng đã bị thay đổi đâu

đó mới đây:

$atime = $mtime = 700000000 ; # thời gian trước đây utime($atime, $mtime, “fred”, “barney”) ;

Không có giá trị “hợp lí” cho nhãn thời gian: bạn có

thể làm cho một tệp trông thực là cũ kĩ như nó đã được

sửa đổi đâu đó trong tương lai xa xôi (có ích nếu bạn

đang viết chuyện khoa học viễn tưởng). Chẳng hạn, dùng

toán tử time (mà cho lại thời gian hiện tại như một nhãn

thời gian UNIX), sau đây là cách làm cho tệp

max_headroom có vẻ như đã được cập nhật 20 phút trong

tương lai:

$when = time + 20*60 ; # 20 phút nữa từ bây giờ utime($when, $when, “max_headroom”) ;

Page 241: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

229

Bài tập

1. Viết một chương trình làm việc giống như rm, xoá đi

các tệp được cho như đối dòng lệnh khi chương trình

này được gọi tới. (Bạn không cần phải giải quyết bất

kì tuỳ chọn nào của rm.)

Kiểm thử cẩn thận chương trình này trong một danh

mục gần rỗng để cho bạn không ngẫu nhiên xoá đi

mất chất liệu có ích! Nhớ rằng đối dòng lệnh thứ

nhất có sẵn trong mảng @ARGV khi chương trình

bắt đầu.

2. Viết một chương trình làm việc giống như mv, đổi

tên đối dòng lệnh thứ nhất thành đối dòng lệnh thứ

hai. (Bạn không cần giải quyết bất kì tuỳ chọn nào

của ln, hay nhiều hơn hai đối dòng lệnh.)

3. Viết một chương trình làm việc giống như ln, tạo ra

một móc nối cứng từ đối dòng lệnh thứ nhất sang đối

dòng lệnh thứ hai. (Bạn không cần giải quyết bất kì

tuỳ chọn nào của ln, hay nhiều hơn hai đối dòng

lệnh.)

4. Nếu bạn có các móc nối tượng trưng, thay đổi

chương trình trong bài tập trước để giải quyết một

khoá tuỳ chọn -s

5. Nếu bạn có móc nối tượng trưng, viết một chương

trình tìm tất cả các tệp có móc nối tượng trưng tương

tự như cách ls -l thực hiện nó (name -> value). Tạo ra

một số móc nối tượng trưng trong danh mục hiện

thời và kiểm thử nó.

Page 242: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

230

14

Quản lí

tiến trình

Dùng system() và exec()

Khi bạn trao cho lớp vỏ một dòng chỉ lệnh để thực

hiện, lớp vỏ tạo ra một tiến trình để thực hiện chỉ lệnh

này. Tiến trình mới này trở thành tiến trình con của lớp

vỏ, thực hiện độc lập và không phối hợp gì với lớp vỏ cả.

Tương tự, một chương trình Perl có thể phát động

các tiến trình mới, và giống như hầu hết các phép toán

khác, có nhiều cách để thực hiện nó.

Cách đơn giản nhất để phát động một tiến trình mới

là dùng toán tử system. Dưới dạng đơn giản nhất của nó,

toán tử này trao một xâu cho lệnh lớp vỏ /bin/sh để được

thực hiện như một chỉ lệnh. Khi chỉ lệnh này được hoàn

Trong chương này:

Dùng system() và exec()

Dùng trích dẫn lùi

Dùng tiến trìn hvà tước hiệu tệp

Dùng fork

Tóm tắt về phép toán tiến trình

Gửi và nhận tín hiệu

Page 243: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

231

thành, toán tử system cho lại giá trị ra của chỉ lệnh (điển

hình là 0 nếu mọi thứ diễn tiến tốt đẹp). Sau đây là một

thí dụ về một chương trình Perl thực hiện chỉ lệnh date

bằng việc dùng lớp vỏ* :

system (“date”) ;

Lưu ý rằng tôi đang không biết gì về giá trị cho lại ở

đây, nhưng chẳng thể nào mà chỉ lệnh date lại không

thực hiện được.

Cái ra sẽ đi đâu? Thực ra, cái vào tới từ đâu, nếu đấy

là một chỉ lệnh muốn đưa vào? Đây là những câu hỏi

hay, và câu trả lời cho những câu hỏi này là phần lớn

những điều phân biệt các dạng khác nhau của việc tạo ra

tiến trình.

Với toán tử system, ba tệp chuẩn (cái vào chuẩn, cái

ra chuẩn, và lỗi chuẩn) đã được kế thừa từ tiến trình Perl.

Cho nên với chỉ lệnh date trong thí dụ trên, cái ra sẽ

chuyển đến bất kì đâu mà print STDOUT đi ra - có lẽ là

trên màn hình của nơi gọi. Bởi vì bạn đang phát hoả ra

lớp vỏ, nên bạn có thể thay đổi vị trí của cái ra chuẩn

bằng việc dùng cách chuyển hướng vào/ra thông thường

/bin/sh. Chẳng hạn, để đặt cái ra của chỉ lệnh date vào

trong một tệp có tên right_now, điều gì đó tựa thế này sẽ

có tác dụng:

system (“date > right_now”) && die “Không thể tạo ra right_now” ;

Lần này, tôi không chỉ gửi cái ra của chỉ lệnh date

* Điều này thực tế không dùng tới lớp vỏ - Perl thực hiện các phép

toán của lớp vỏ nếu dòng chỉ lệnh đủ đơn giản, và đây là một trường

hợp.

Page 244: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

232

vào một tệp với việc đổi hướng sang lớp vỏ, mà tôi còn

kiểm tra trạng thái cho lại. Nếu trạng thái cho lại là đúng

(khác không), cái gì đó bị sai với chỉ lệnh lớp vỏ, và toán

tử die sẽ tiến hành hành động của nó. Đây là nhược điểm

theo qui ước toán tử thông thường của Perl - giá trị cho

lại khác không từ toán tử system nói chung chỉ ra rằng

điều gì đó bị sai.

Đối cho toán tử system có thể là bất kì cái gì mà bạn

sẽ nạp vào /bin/sh, cho nên nhiều chỉ lệnh có thể được

bao hàm vào, tách biệt nhau bằng dấu chấm phẩy hoặc

dòng mới. Và các tiến trình kết thúc trong & sẽ được

khởi động và không đợi, dù cho bạn đã gõ một dòng kết

thúc với một & vào lớp vỏ.

Sau đây là một thí dụ về việc sinh ra chỉ lệnh date và

who cho lớp vỏ, gửi cái ra tới tên tệp được xác định bởi

một biến Perl. Tất cả điều này xảy ra trong hậu cảnh sao

cho chúng không phải đợi nó trước khi tiếp tục với bản

ghi Perl:

$where = “who_out.”.++$i ; # lấy một tên tệp mới system (“(date; who) > $where &”) ;

Giá trị cho lại từ system trong trường hợp này là giá

trị ra của lớp vỏ, và do vậy chỉ ra liệu tiến trình hậu cảnh

đã khởi động thành công hay chưa, nhưng không chỉ ra

liệu các chỉ lệnh date hay who có thực hiện thành công

hay không. Xâu nháy kép là một biến xen lẫn, cho nên

$where được thay thế bằng giá trị của nó trước khi lớp vỏ

thấy nó. Nếu bạn muốn tham chiếu tới biến lớp vỏ có

thên $where, bạn phải đặt dấu sổ chéo ngược trước dấu

đô la, hay dùng một xâu nháy đơn, hay một cái gì đó

giống thế.

Page 245: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

233

Một tiến trình con kế thừa nhiều điều từ cha mẹ nó

bên cạnh các tước hiệu tệp chuẩn. Những tiến trình này

bao gồm việc bỏ mặt nạ hiện tại, danh mục hiện tại, và

tất nhiên cả ID người dùng.

Bên cạnh đó, các biến môi trường cũng được tiến

trình con kế thừa. Các biến này được thay đổi điển hình

qua chỉ lệnh csh setenv hay phép gán tương ứng và

export bởi lớp vỏ /bin/sh. Các biến môi trường được

nhiều trình tiện ích dùng, kể cả lớp vỏ, để thay đổi hay

kiểm soát cách thức trình tiện ích này vận hành.

Perl cho bạn một cách kiểm tra và thay đổi biến môi

trường hiện tại qua một mảng kết hợp buồn cười gọi

là %ENV (chữ hoa). Mỗi khoá của mảng này tương ứng

với tên của một biến môi trường, với giá trị tương ứng,

tất nhiên, là giá trị tương ứng. Việc xem xét mảng này

ngay đầu chương trình chỉ ra cho bạn môi trường được

trao cho Perl bởi lớp vỏ cha mẹ - việc thay đổi mảng này

ảnh hưởng tới môi trường do Perl sử dụng và cho các

tiến trình con của nó.

Chẳng hạn, sau đây là một chương trình đơn giản

hành động giống như printenv:

for $key (sort keys %ENV) { print “$key = $ENV{$key}\n” ; }

Lưu ý rằng dấu bằng ở đây không phải là phép gán,

nhưng đơn giản là một kí tự văn bản mà print dùng để in

ra chất liệu như TERM=xterm hay USER=merlyn.

Sau đây là một chương trình nhãi ranh làm thay đổi

giá trị của PATH để bảo đảm rằng chỉ lệnh grep do

system cho chạy được tìm tới chỉ ở những chỗ thông

Page 246: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

234

thường:

$oldPATH = $ENV{“PATH”} ; # cất giữ đường dẫn trước $ENV{“PATH”} = “/bin:/usr/bin:/usr/ucb” ; # buộc đường

dẫn đã biết system (“grep fred bedrock > output”) ; cho chạy chỉ lệnh $ENV{“PATH”} = $oldPATH ; # khôi phục lại đường dẫn

trước

Toán tử system cũng có thể nhận một danh sách đối,

thay vì một đối. Trong trường hợp này, thay vì để cho

lớp vỏ diễn giải danh sách đối, Perl xử lí đối thứ nhất

như một chỉ lệnh để chạy (được định vị tương ứng theo

PATH nếu cần thiết) và các đối còn lại như các đối cho

chỉ lệnh không có diễn giải lớp vỏ thông thường. Nói

cách khác, bạn không cần trích dẫn khoảng trắng hay lo

lắng về các đối có chứa dấu ngoặc nhọn bởi vì tất cả

những cái đó đơn thuần chỉ là các kí tự để truyền cho

chương trình. Vậy, hai chỉ lệnh sau đây là tương đương:

system “grep ‘fred flƯnttone’ buffaloes” ; # dùng lớp vỏ system “grep”, “fred flƯnttone”, “buffaloes” ; # dùng danh

sách

Cho một danh sách chứ không cho một xâu đơn sẽ

tiết kiệm cho bạn một tiến trình lớp vỏ, cho nên làm điều

này khi bạn có thể. (Thực ra, khi dạng một đối của

system là đủ đơn giản, Perl tối ưu toàn bộ lời gọi lớp vỏ,

gọi chương trình kết quả một cách trực tiếp dường như

bạn đã dùng lời gọi nhiều đối.)

Sau đây là một thí dụ khác về các dạng tương

đương:

@cfiles = ( “fred.c”, “barney.c”) ; # tệp phải dịch @options = ( “-DHARD”, “-DGRANITE”) ; # tuỳ chọn system “cc -o slate @option @cfiles” ; # dùng lớp vỏ system “cc, “-o”, “slate”, @options, @cfiles ; # dùng danh

Page 247: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

235

sách

Dùng dấu nháy đơn ngược

Một cách khác để phát động một tiến trình là đặt

dòng chỉ lệnh vỏ /bin/sh giữa các dấu nháy đơn ngược.

Giống như lớp vỏ, dòng lệnh này phát ra một chỉ lệnh và

đợi việc hoàn tất, đưa ra đầu ra chuẩn. Không giống như

lớp vỏ, văn bản kết quả không được mở rộng tại chỗ (trở

thành nhiều cái vào của kết cấu Perl) mà thay vì thế trở

thành giá trị của xâu nháy đơn ngược. Chẳng hạn:

$snow = “Thời gian bây giờ là ” . `date` ; # lấy văn bản và ngày đưa ra

Giá trị của $now sẽ là văn bản Thời gian bây giờ là

cùng với kết quả của chỉ lệnh date (kể cả dấu xuống

dòng cuối cùng), cho nên nó giống như:

Thời gian bây giờ là Fri Aug 13 23:59:59 PDT 1993

Nếu chỉ lệnh dấu nháy đơn ngược được dùng trong

hoàn cảnh mảng chứ không phải hoàn cảnh vô hướng,

bạn thu được một danh sách các xâu, mỗi một xâu là một

dòng (được kết thúc với dấu xuống dòng) từ cái ra của

chỉ lệnh. Với thí dụ về date, chúng chỉ có một phần tử,

bởi vì nó chỉ sinh ra một dòng văn bản. Cái ra của who,

trong giống thế này:

merlyn tty13 Sep 1 14:55 fred tty1A Aug 31 07:02 barney tty1F Sep 1 09:22

Sau đây là cách lấy cái ra này trong ngữ cảnh mảng:

foreach $_ (`who`) { # một lần cho mỗi dòng văn bản từ who

Page 248: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

236

($who, $where, $when) = / (\S+)\s+(\S+)\s+(.*)/ ; print “$who trên $where tại $when\n” ; }

Mỗi bước qua chu trình đã làm việc trên một dòng

tách biệt của cái ra của who, bởi vì chỉ lệnh dấu ngoặc

đến ngược được tính bên trong hoàn cảnh mảng.

Cái vào chuẩn và lỗi chuẩn của chỉ lệnh bên trong

dấu nháy đơn ngược là được kế thừa từ tiến trình Perl.

Điều này có nghĩa là bạn thông thường chỉ lấy được cái

ra chuẩn của các chỉ lệnh bên trong dấu nháy đơn ngược

như giá trị của xâu dấu nháy đơn ngược. Một điều thông

dụng thường làm là gộp lỗi chuẩn vào trong cái ra chuẩn

để cho chỉ lệnh dấu nháy đơn ngược lấy cả hai, dùng kết

cấu 2>&1 của lớp vỏ:

die “rm mawi!” if ‘rm fred 2>&1’ ;

Tại đây, tiến trình Perl được kết thúc nếu rm không

nói gì, hoặc nói với cái ra chuẩn và lỗi chuẩn, bởi vì kết

quả sẽ không là một xâu rỗng (xâu rỗng sẽ sai).

Dùng các tiến trình như tước hiệu tệp

Còn cách khác để phát động một tiến trình là tạo ra

một tiến trình giống như một tước hiệu tệp (tương tự như

trình thư viện popen nếu bạn quen thuộc với nó). Bởi vì

tước hiệu tệp mở để hoặc đọc hoặc ghi, nên chúng ta có

thể tạo ra một tước hiệu tệp-tiến trình mà hoặc lấy cái ra

từ hoặc cung cấp cái vào cho tiến trình. Sau đây là một

thí dụ về việc tạo ra một tước hiệu từ tiến trình who. Bởi

vì tiến trình này đang sinh ra cái ra nên chúng ta tạo ra

một tước hiệu mở để đọc, như:

Page 249: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

237

open (WHOPROC, “who |”) ; # mở để đọc

để ý đến thanh sổ đứng ở phía bên phải của who.

Thanh đó báo cho Perl rằng open này không phải là tên

tệp, mà thay vào đó là chỉ lệnh cần bắt đầu. Bởi vì thanh

này là bên phải của chỉ lệnh, nên tước hiệu tệp được mở

để đọc, có nghĩa là cái ra chuẩn của who được dự định bị

bắt giữ. (Cái vào chuẩn và lỗi chuẩn vẫn còn được dùng

chung với tiến trình Perl.) Với phần còn lại của chương

trình này, WHOPROC giải quyết đơn thuần tước hiệu

tệp mà đã mở để đọc, có nghĩa là tất cả các toán tử

vào/ra tệp thông thường đã áp dụng được. Sau đây là

cách đọc dữ liệu từ chỉ lệnh who vào mảng:

@whosaid = <WHOPROC> ;

Tương tự, mở một chỉ lệnh mà trông đợi cái vào,

chúng ta có thể mở một tước hiệu tệp-tiến trình để ghi

bằng việc đặt thanh sổ đứng ở bên trái của chỉ lệnh, như:

open(LPR, “| lpr -Pslatewrite”) ; print LPR @rockreport ; close(LPR) ;

Lưu ý rằng trong trường hợp này, sau việc mở LPR,

chúng ta ghi dữ liệu nào đó lên nó, và rồi đóng nó lại.

Việc mở một tiến trình với tước hiệu tệp-tiến trình cho

phép chỉ lệnh được thực hiện song song với chương trình

Perl. Việc nói close() trên tước hiệu tệp này buộc chương

trình Perl phải đợi đến khi tiến trình này ra. Nếu bạn

không đóng tước hiệu tệp, tiến trình này có thể tiếp tục

chạy thậm chí bên ngoài việc thực hiện chương trình

Perl.

Việc mở một tiến trình để ghi sẽ làm cho đầu vào

chuẩn tới từ tước hiệu tệp. Tiến trình này dùng chung cái

Page 250: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

238

ra chuẩn và lỗi chuẩn với Perl. Như trước đây, bạn có thể

dùng việc định hướng vào-ra kiểu /bin/sh, cho nên ở đây

có một cách đơn giản để lược bỏ thông báo lỗi từ chỉ

lệnh lpr trong thí dụ trước:

open(LPR, “| lpr -Pslatewrite > /dev/null 2>&1”) ;

/dev/null gây ra cái ra chuẩn được định hướng lại tới

thiết bị không. 2>&1 gây cho lỗi chuẩn được gửi tới chỗ

cái ra chuẩn được gửi tới, kết quả lỗi sẽ bị cắt bỏ đi.

Bạn thậm chí có thể tổ hợp tất cả những điều này lại,

sinh ra một báo cáo về mọi người chỉ trừ Fred trong danh

sách các ô đã vào, kiểu như:

open(WHO, “who |”) ; open (LPR, “| lpt -Pslatewrite”) ; while (<WHO>) { unless (/fred/) { # không đưa ra fred print LPR $_ ; } } close (WHO) ; close (LPR) ;

Khi đoạn chương trình này đọc từ tước hiệu WHO

một dòng mỗi lúc, nó in ra tất cả các dòng mà không

chứa xâu fred cho tước hiệu LPR. Cho nên chỉ cái ra trên

máy in mới là dòng không chứa fred.

Dùng fork

Vẫn còn một cách khác để tạo ra một tiến trình phụ

là bám theo một tiến trình Perl hiện tại bằng việc dùng

nguyên sơ UNIX có tên là fork. Toán tử fork đơn giản làm

điều mà lời gọi hệ thống fork thực hiện; toán tử này tạo ra

Page 251: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

239

một bản bám theo tiến trình hiện tại. Bản bám theo này

(được gọi là con, với bản gốc được gọi là bố mẹ) dùng

chung cùng chương trình thực hiện, biến, và thậm chí cả

các tệp đã mở. Để phân biệt hai tiến trình này, giá trị cho

lại từ toán tử fork là không đối với con, và khác không

đối với bố mẹ. Bạn có thể kiểm tra điều này và hành

động tương ứng:

if (fork) { # tôi là bố mẹ } else { # tôi là con }

Đẻ dùng tốt nhất bản bám theo, chúng ta cần học về

vài điều mới nữa mà song song với các thành phần cùng

tên trong UNIX: các toán tử wait, exit và exec.

Toán tử đơn giản nhất là exec. Nó cũng giống như

toán tử system, ngoại trừ rằng thay vì dùng thực hiện tiến

trình mới để thực hiện chỉ lệnh vỏ, Perl thay thế tiến

trình hiện tại với lớp vỏ. (Trong cách nói UNIX, Perl

thực hiện vỏ). Sau khi toán tử exec thực hiện thành công,

chương trình Perl kết thúc, bị thay thế bởi chương trình

được yêu cầu. Chẳng hạn:

exec “date” ;

thay thế chương trình Perl hiện tại bằng chỉ lệnh

date, làm cho cái ra của date chuyển thẳng ra cái ra

chuẩn của chương trình Perl. Khi chỉ lệnh date kết thúc,

không còn gì phải thực hiện nữa bởi vì chương trình Perl

đã xong.

Một cách khác để nhìn vào điều này là ở chỗ toán tử

system giống như fork có theo sau một exec, như sau:

Page 252: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

240

# Phương pháp 1... dùng hệ thống: system (“date”) ; # Phương pháp 2... dùng fork/exec unless (fork) { # fork cho không, cho nên tôi là con và tôi exec: exec (“date”) ; # tiến trình con trở thành chỉ lệnh date }

Việc dùng fork và exec theo cách này không hoàn

toàn phải lắm, bởi vì chỉ lệnh date và tiến trình bố mẹ cả

hai đã chạy đồng thời, có thể đan chéo cái ra và làm mọi

sự lẫn lộn. Điều cần là cách để báo cho tiến trình bố mẹ

đợi cho tới khi tiến trình con hoàn tất. Điều đó được xác

là điều toán tử wait thực hiện - nó đợi cho tới khi tiến

trình con (bất kì con nào, cần phải chính xác) đã hoàn

tất:

unless (fork) { # fork cho không, cho nên tôi là con và tôi exec: exec (“date”) ; # tiến trình con trở thành chỉ lệnh date } wait ; # bố mẹ đợi cho con (date) hoàn tất

Nếu tất cả điều này dường như khá mờ với bạn, bạn

có lẽ nên nghiên cứu các lời gọi hệ thống fork và exec

trong sách UNIX truyền thống, vì Perl thường lấy khá

trực tiếp lời gọi hệ thống UNIX chuyển sang.

Toán tử exit() tạo nên việc đi ra tức khắc khỏi tiến

trình Perl hiện tại. Bạn đã dùng cách này để bỏ chương

trình Perl từ đâu đó ở giữa, hay với toán tử fork để thực

hiện chương trình Perl nào đó đang tiến hành và rồi ra.

Sau đây là một trường hợp của việc loại bỏ một số tệp

trong /tmp trên nền tảng dùng một tiến trình Perl chẽ ra.

unless (fork) { # Tôi là tiến trình con

unkink < /tmp/bedrock.* > ; # phá các tệp này

Page 253: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

241

exit ; # tiến trình con dùng thực hiện ở đây } # Tiến trình bố mẹ tiếp tụ ở đây

Không có exit, tiến trình con vẫn tiếp tục thực hiện

chương trình Perl (tại dòng đã đánh dấu Tiến trình bố mẹ

tiếp tục ở đây) và đó dứt khoát không phải là điều chúng

ta muốn.

Toán tử exit nhận một tham biến phụ, phục vụ như

giá trị ra số mà có thể được tiến trình bố mẹ lưu ý tới.

Giá trị mặc định là cho việc ra với giá trị không, chỉ ra

rằng mọi thứ đã ổn thoả.

Tóm tắt về các phép toán tiến trình

Bảng 14-1 tóm tắt các phép toán mà bạn có để khởi

động tiến trình.

Bảng 14-1 Tóm tắt về các phép toán tiến trình

Phép toán Cái vào

chuẩn

Cái ra

chuẩn

Lỗi

chuẩn

Cần đợi

không?

system()

Kế thừa từ chương trình

Kế thừa từ chương trình

Kế thừa từ chương trình

xâu nháy đơn ngược

Kế thừa từ chương trình

được lấy như giá trị xâu

Kế thừa từ chương trình

chỉ lệnh open() xem như tước hiệu tệp cho cái ra

Được nối với tước hiệu tệp

Kế thừa từ chương trình

Kế thừa từ chương trình

Chỉ vào lúc close()

Page 254: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

242

Phép toán Cái vào

chuẩn

Cái ra

chuẩn

Lỗi

chuẩn

Cần đợi

không?

chỉ lệnh open() xem như tước hiệu tệp cho cái vào

Kế thừa từ chương trình

Được nối với tước hiệu tệp

Kế thừa từ chương trình

Chỉ vào lúc close()

fork, exec, wait

Người dùng lựa chọn

Người dùng lựa chọn

Người dùng lựa chọn

Người dùng lựa chọn

Việc tạo tiến trình đơn giản nhất là với toán tử

system. Cái vào, cái ra chuẩn và lỗi chuẩn đã không bị

ảnh hưởng (chúng ta được kế thừa từ tiến trình Perl).

Một xâu nháy đơn ngược tạo ra một tiến trình, lấy cái ra

chuẩn của tiến trình này như một giá trị xâu cho chương

trình Perl. Cái vào chuẩn và lỗi chuẩn không bị ảnh

hưởng. Cả hai phương pháp này đã đòi hỏi rằng tiến

trình này kết thúc trước khi bất kì chương trình Perl nào

được thực hiện.

Một cách đơn giản để có tiến trình dị bộ (tiến trình

mà cho phép chương trình Perl tiếp tục trước khi tiến

trình này được hoàn tất) là để mở một chỉ lệnh như một

tước hiệu tệp, tạo ra một đường ống cho cái vào chuẩn

hay cái ra chuẩn của chỉ lệnh. Một chỉ lệnh được mở như

tước hiệu tệp để đọc sẽ kế thừa cái vào chuẩn và lỗi

chuẩn từ chương trình Perl; một chỉ lệnh được mở như

một tước hiệu tệp để ghi sẽ kế thừa cái ra chuẩn và lỗi

chuẩn từ chương trình Perl.

Cách mềm dẻo nhất để bắt đầu một tiến trình là để

cho chương trình của bạn gọi các toán tử fork, exec và

Page 255: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

243

wait, mà tương ứng trực tiếp với các tên lời gọi hệ thống

UNIX. Bằng việc dùng các toán tử này, bạn có thể chọn

liệu bạn có chờ đợi hay không, và đặt cấu hình cho cái

vào, cái ra và lỗi chuẩn theo bất kì cách nào bạn chọn* .

Gửi và nhận tín hiệu

Một phương pháp cho việc truyền thông liên tiến

trình là gửi và nhận tín hiệu. Tín hiệu là thông báo một

bit (có nghĩa là “tín hiệu này đã xảy ra”) được gửi cho

một tiến trình từ một tiến trình khác hoặc từ lõi UNIX.

Các tín hiệu được đánh số, thông thường từ một tới một

số nhỏ hơn 15 hay 31. Một số tín hiệu đã có ý nghĩa định

trước và được gửi một cách tự động cho một tiến trình

dưới một điều kiện nào đó (như lỗi bộ nhớ hay ngoại lệ

dấu phẩy động) - một số khác riêng do người dùng sinh

ra từ các tiến trình khác. Các tiến trình này phải có phép

để gửi tín hiệu như vậy. Nói chung, nếu tiến trình có

cùng userID, tín hiệu là được phép.

Việc đáp ứng cho một tín hiệu được gọi là hành

động của tín hiệu đó. Các tín hiệu định sẵn có những

hành động mặc định có ích nào đó, như bỏ tiến trình hay

cho chạy tiếp. Các tín hiệu khác hoàn toàn bị bỏ qua theo

mặc định. Gần như tất cả các tín hiệu đã có thể có hành

động mặc định của nó bị thay đổi, hoặc là bị bỏ qua hoặc

bị bắt giữ (gọi phần chương trình do người dùng xác

định một cách tự động).

Cho tới nay, tất cả điều này đã là chất liệu kiểu

* Mặc dầu cũng có thể có ích là biết về open (STDERR,

“>&STDOUT”) cho lại tước hiệu tệp.

Page 256: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

244

UNIX - tại đây nó mang theo nghĩa riêng của Perl. Khi

một tiến trình Perl bắt lấy một tín hiệu, một chương trình

con do bạn chọn sẽ được gọi một cách dị bộ và tự động,

tạm thời ngắt bất kì cái gì đang được thực hiện. Khi

chương trình con này ra, bất kì cái gì đang được thực

hiện cũng đã được chạy lại dường như không có gì xảy

ra cả (ngoại trừ các hành động được thực hiện bởi

chương trình con con này, nếu có).

Một cách điển hình, chương trình con bắt tín hiệu sẽ

thực hiện một trong hai điều: bỏ chương trình sau khi

thực hiện một số việc dọn sạch, hoặc đặt cờ nào đó (như

biến toàn cục) mà chương trình này đã đặn kiểm tra.

Bạn cần biết tín hiệu nào được đặt tên để đóng kí

cho bộ giải quyết tín hiệu với Perl. Bằng việc đóng kí

với bộ giải quyết tín hiệu, Perl sẽ gọi chương trình con

được lựa chọn khi nhận được tín hiệu này.

Các tên tín hiệu được định nghĩa trong tài liệu lời

gọi hệ thống signal, và thông thường cũng có trong tệp

bao hàn của C /usr/include/sys/signal.h. Các tên nói

chung được bắt đầu với SIG, như SIGINT, SIGQUIT và

SIGKILL. Để khai báo chương trình con

&my_sigint_catcher như bộ xử lí tín hiệu giải quyết với

SIGINT, chúng ta đặt một giá trị vào trong mảng kết

hợp %SIG ảo thuật. Trong mảng này, chúng ta đặt một

giá trị của khoá INT (đó là SIGINT không có SIG) cho

tên của trình con mà sẽ bắt tín hiệu SIGINT, kiểu như:

$SIG {‘INT’ } = ‘my_sigint_catcher’ ;

Nhưng chúng ta cũng cần một định nghĩa cho trình

con đó. Sau đây là một thí dụ đơn giản:

sub my_sigint_catcher {

Page 257: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

245

$saw_signit = 1 ; # đặt cờ }

Bộ bắt tín hiệu này đặt một biến toàn cục, và rồi ra

ngay lập tức. Việc trở về từ trình con này gây ra việc

thực hiện lại bất kì chỗ nào nó đã bị ngắt. Một cách điển

hình, trước hết bạn đặt không cho cờ $saw_sigint, đặt

trình con này như bộ bắt SIGINT, và rồi thực hiện trình

bạn vẫn chạy, kiểu như:

$saw_sigint = 0 ; # xoá cờ $SIG {‘INT’ } = ‘my_sigint_catcher’ ; # đóng kí bộ bắt foreach (@huge_array) { # làm điều gì đó # làm thêm vài việc # vẫn làm thêm vài việc if ($saw_sigint) { # cần ngắt không? # dọn dẹp gì đó ở đây last ; } } $SIG {‘INT’} = ‘DEFAULT’ ; # khôi phục hành động mặc

định

Mẹo ở đây là chỗ giá trị của cờ này được kiểm tra

tại những điểm có ích trong khi tính toán và được dùng

để ra khỏi chu trình khi chưa xong, tại đây cũng giải

quyết một số hành động dọn dẹp. Lưu ý tới câu lệnh cuối

trong chương trình trên: đặt hành động DEFAULT khôi

phục lại hành động ngầm định trên một tín hiệu đặc biệt

(SIGINT khác sẽ bỏ chương trình này ngay lập tức). Một

giá trị có ích đặc biệt khác giống thế này là IGNORE, có

nghĩa là bỏ qua tín hiệu này (nếu hành động mặc định

không bỏ qua tín hiệu đó, như SIGINT). Bạn có thể làm

một hành động tín hiệu IGNORE nếu không cần hành

động dọn dẹp nào, và bạn không muốn kết thúc các phép

Page 258: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

246

toán sớm.

Một trong nhiều cách để tín hiệu SIGINT được sinh

ra là bằng việc để cho người dùng nhấn vào một kí tự

ngắt đã chọn (như Delete hay Control-C) trên bàn phím.

Nhưng một tiến trình cũng có thể sinh ra tín hiệu

SIGINT trực tiếp bằng việc dùng toán tử kill. Toán tử này

nhận một số hiệu hay tên tín hiệu, và gửi tín hiệu đó cho

danh sách các tiến trình được cho theo tín hiệu này. Cho

nên việc gửi một tín hiệu từ một chương trình đòi hỏi

phải xác định số hiệu tiến trình của các tiến trình nhận

(số hiệu tiến trình được cho lại từ một số các toán tử,

như fork hay việc mở một chương trình như một tước

hiệu tệp). Giả sử bạn muốn gửi một tín hiệu 2 (cũng còn

được biết như SIGINT) cho các tiến trình có số hiệu 234

và 237. Đơn giản hơn cả là như thế này:

kill (2, 234, 237) ; # gửi SIGINT cho 234 và 237

Bài tập

Xem trả lời ở phụ lục A

1. Viết một chương trình phân tích cái ra của chỉ lệnh

date để thu được ngày hiện tại của tuần. Nếu ngày

của tuần là ngày làm việc, in chữ làm việc, ngoài ra in

chữ đi chơi.

2. Viết một chương trình nhận tất cả các tên thực của

người dùng từ tệp /etc/passwd, rồi biến đổi thành cái

ra của chỉ lệnh who, bằng việc thay thế tên đăng nhập

(cột thứ nhất) bởi tên thật. (Hướng dẫn: tạo ra một

mảng kết hợp có khoá là tên đăng nhập và giá trị là

Page 259: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

247

tên thật.) Thử cả hai với chỉ lệnh who trong trường

hợp dấu nháy đơn ngược và được mở như đường

ống. Cái nào dễ hơn?

3. Sửa đổi chương trình trên để cho cái ra tự động đi ra

máy in.

4. Giả sử hàm mkdir() bị hỏng. Viết một trình con mà

không dùng mkdir(), nhưng thay vào đó gọi /bin/mkdir

bằng system(). (Phải chắc rằng nó làm việc với các

danh mục có một dấu cách trong tên.)

5. Mở rộng trình từ bài tập trước để sử dụng chmod() để

đặt phép sử dụng.

Page 260: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

248

Page 261: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

249

15

Biến đổi

dữ liệu khác

Tìm một xâu con

Tìm một xâu con phụ thuộc vào nơi bạn mất nó. Nếu

bạn ngẫu nhiên mất nó bên trong một xâu lớn hơn, bạn

còn may mắn, vì index() có thể giúp bạn tìm ra. Sau đây

là dáng vẻ của nó:

$x = index ($string, $substring) ;

Perl định vị lần xuất hiện đầu tiên của substring bên

trong string, cho lại một số nguyên chỉ vị trí của kí tự đầu

tiên. Giá trị chỉ số này được cho lại dựa trên không - tức

là nếu tìm được substring ở chỗ bắt đầu của string, bạn

nhận được 0. Nếu nó là ở một kí tự sau đó, bạn nhận

được 1, và cứ như thế. Nếu không tìm thấy substring

Trong chương này:

Tìm và thay thế

Trích và thay thế xâu con

Định dạng dữ liệu dùng sprìnt()

Sắp xếp nâng cao

Chuyển tự

Page 262: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

250

trong string, bạn nhận được -1.

xem những điều sau:

$where = index(“hello”, “e”); # $where nhận 1 $oerson = “barney”; $where = index(“fred barney”, $person) ; # $where nhận

5 @rockers = (“fred”, “barney”) ; $where = index(join(“ “, @rockers), $person); # cũng thế

Chú ý rằng cả hai xâu này đã được tìm kiếm và xâu

được tìm kiếm, có thể là một hằng xâu kí hiệu, một biến

vô hướng có chứa một xâu, hay thậm chí một biểu thức

có giá trị xâu vô hướng. Sau đây là một số thí dụ nữa:

$which = index(“a very long string”, “long”); # $switch nhận 7

$which = index(“a very long string”, “lame”); # $switch nhận -1

Nếu xâu có chứa xâu con tại nhiều vị trí, toán tử

index() sẽ cho lại vị trí bên trái nhất. Để tìm ra các vị trí

sau, bạn có thể cho index() tham biến thứ ba. Tham biến

này là giá trị tối thiểu mà index() sẽ cho lại, cho phép bạn

tìm lần xuất hiện tiếp của xâu con theo sau một vị trí đã

chọn. Nó trông tựa như thế này:

$x = index($bigtring, $littlestring, $skip);

Sau đây là một số thí dụ về cách tham biến thứ ba

làm việc:

$where = index(“hello world”, “l”); # cho lại 2 (l đầu tiên) $where = index(“hello world”, “l”, 0); # cũng thế $where = index(“hello world”, “l”, 1); # vẫn thế $where = index(“hello world”, “l”,3); # bây giờ cho lại 3 # (3 là vị trí đầu tiên lớn hơn hay bằng 3) $where = index(“hello world”, “o”, 5); # cho lại 7 (0 thứ

Page 263: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

251

hai) $where = index(“hello world”, “o”, 8); # cho lại -1 (hết sau

8)

Đi theo lối khác, bạn có thể quét từ bên phải để có

được sự xuất hiện bên phải nhất bằng việc dùng rindex().

Giá trị cho lại vẫn là số các kí tự giữa đầu bên trái của

xâu và chỗ bắt đầu của xâu con, như trước, nhưng bạn sẽ

nhận được sự xuất hiện về bên phải nhất thay vì sự xuất

hiện bên trái nhất nếu có nhiều sự xuất hiện. Toán tử

rindex() cũng nhận tham biến thứ ba giống như index() ,

để cho bạn có thể có được sự xuất hiện ít hơn hay bằng

vị trí đã chọn. Sau đây là một số thí dụ về điều bạn nhận

được:

$w = rindex(“hello world”, “he”); # $w nhận 0 $w = rindex(“hello world”, “l”); # $w nhận 9 (l bên phải

nhất) $w = rindex(“hello world”, “o”); # $w nhận 7 $w = rindex(“hello world”, “o ”); # $w nhận 4 $w = rindex(“hello world”, “xx”); # $w nhận -1 (không

thấy) $w = rindex(“hello world”, “o”, 6); # $w nhận 4 (đầu tiên

trước 6) $w = rindex(“hello world”,“o”, 3); # $w nhận -1 (không

thấy trước 3)

Trích và thay thế một xâu con

Việc lấy ra một mẩu của xâu có thể được thực hiện

bằng việc áp dụng cẩn thận các biểu thức chính qui,

nhưng nếu mẩu này bao giờ cũng ở tại một vị trí kí tự đã

biết, việc này là không hiệu quả. Thay vì vậy, bạn nên

dùng substr(). Toán tử này nhận ba đối: một giá trị xâu,

một vị trí bắt đầu (được đo tựa như nó đã được đo cho

Page 264: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

252

index()), và một chiều dài, giống như:

$s = substr ($string, $start, $length);

Vị trí bắt đầu làm việc giống như index; kí tự đầu

tiên là không, kí tự thứ hai là một, và cứ thế. Chiều dài là

số các kí tự cần nắm lấy tại điểm đó: chiều dài bằng

không có nghĩa là không có kí tự nào, bằng một có nghĩa

là lấy kí tự đầu tiên, bằng hai có nghĩa là hai kí tự, và

vân vân. (Nó dùng lại tại cuối xâu, cho nên nếu bạn tìm

kiếm quá nhiều, cũng không sao cả.) Nó trông giống thế

này:

$hello = “hello, world!”; $grab = substr($hello, 3, 2); # $grap nhận “lo” $grab = substr($hello, 7, 100); # 7 đến cuối, hay “world!”

Bạn thậm chí có thể tạo ra toán tử “nâng lên luỹ thừa

mười” cho các số mũ nguyên nhỏ, như trong:

$big = substr(“10000000000”, 0, $power+1); # 10**$power

Nếu số đếm các kí tự là không hay bé hơn không,

một xâu rỗng sẽ được cho lại. Mặt khác, nếu vị trí bắt

đầu là bé hơn không, vị trí bắt đầu được tính theo số các

kí tự từ cuối xâu. Cho nên giá trị -1 đối với vị trí bắt đầu

và 1 (hay nhiều) đối với chiều dài sẽ cho bạn kí tự cuối.

Tương tự, -2 cho vị trí bắt đầu với kí tự thứ hai kể từ

cuối. Giống thế:

$stuff = substr(“a very long string”, -3, 3); # ba kí tự cuối $stuff = substr(“a very long string”, -3, 1); # kí tự “i”

Nếu vị trí bắt đầu là trước chỗ mở đầu của xâu

(giống như một số âm khổng lồ lớn hơn chiều dài của

xâu), chỗ mở đầu sẽ là vị trí bắt đầu (dường như bạn đã

dùng 0 làm vị trí bắt đầu). Nếu vị trí bắt đầu là một số

Page 265: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

253

dương khổng lồ, xâu rỗng bao giờ cũng được cho lại.

Nói cách khác, nó có thể làm điều bạn trông đợi nó phải

làm, chừng nào bạn còn trông đợi, nó bao giờ cũng cho

lại một cái gì đó khác hơn là một lỗi.

Bỏ đi đối chiều dài, cũng hệt như bạn đã đưa một số

khổng lồ vào cho đối đó - nắm lấy mọi thứ từ vị trí đã

chọn cho tới cuối xâu* .

Nếu đối thứ nhất của substr() là một biến (nói cách

khác, nó có thể xuất hiện bên vế trái của toán tử gán),

bản thân substr() cũng có thể xuất hiện ở vế bên trái của

toán tử gán. Điều này trông có vẻ kì lạ nếu bạn bắt

nguồn từ nền tảng C, nhưng nếu bạn đã chơi với vài dị

bản của BASIC, nó hoàn toàn là thông thường.

Điều nhận được sự thay đổi như kết quả của phép

gán như thế là phần của xâu sẽ được cho lại, mà có

substr() được dùng trong một biểu thức. Nói cách khác,

substr($var, 3, 2) cho lại kí tự thứ tư và thứ năm (bắt đầu

từ 3, vì số đếm 2), cho nên việc gán điều đó làm thay đổi

hai kí tự này cho $var. Giống như:

$hw = “hello world!”; substr($hw, 0, 5) = “howdy”; # $hw bây giờ là “howdy

world!”

Chiều dài của văn bản thay thế (cái nhận được việc

gán vào trong substr) không phải là cùng như văn bản

được thay thế, như trong thí dụ này. Xâu này sẽ tự động

tăng trưởng hay co lại khi cần để điều hoà với văn bản.

* Các bản Perl cổ hơn không cho phép bỏ đi đối thứ ba, dẫn tới việc

những lập trình viên Perl tiền phong đã dùng số khổng lồ cho đối

đó. Bạn có thể vượt qua điều này trong cuộc hành trình khảo cổ Perl

của mình.

Page 266: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

254

Sau đây là một thí dụ về việc xâu thành ngắn hơn:

substr($hw, 0, 5) = “hi”; # $hw bây giờ là “hi world!”

và đây là một xâu thành dài hơn:

substr($hw, -6, 5) = “worldwide news”; # thay thế “world”

Việc co lại hay dãn ra, khá hiệu quả, cho nên bạn

đừng lo lắng về việc dùng chúng một cách bất kì, mặc

dầu việc thay thế một xâu bằng một xâu chiều dài tương

đương, vẫn nhanh hơn nếu bạn có cơ hội.

Dạng thức dữ liệu bằng sprintf()

Toán tử printf đôi khi cũng dễ sử dụng khi được dùng

để lấy một danh sách các giá trị và tạo ra một dòng ra

cho hiển thị các giá trị đó theo cách điều khiển được.

Toán tử sprintf() là đồng nhất với printf về các đối, nhưng

cho lại bất kì cái gì đã được printf đưa ra như một xâu

riêng biệt. (Nghĩ về điều này như “xâu printf”.) Chẳng

hạn, để tạo ra một xâu bao gồm chữ X theo sau bởi một

giá trị năm chữ số lấp đầy bởi không của $y, cũng dễ

dàng như:

$result = sprintf(“X%05d”, $y);

Nếu bạn không quen thuộc với xâu dạng thức của

printf và sprintf, tham chiếu tài liệu printf hay sprintf. (Tên

sprintf, thực ra có nguồn gốc từ trình thư viện cùng tên.)

Sắp xếp nâng cao

Trước đây, bạn đã biết rằng bạn có thể lấy một danh

sách rồi sắp xếp nó theo thứ tự ASCII tăng dần (như các

Page 267: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

255

xâu), bằng cách dùng toán tử sort có sẵn. Điều gì sẽ xảy

ra nếu bạn không muốn sắp xếp theo thứ tự ASCII tăng

dần, mà thay vì thế là một cái gì đó khác, kiểu như sắp

xếp số? Được, Perl cho bạn công cụ bạn cần để làm việc

này. Thực ra, bạn sẽ thấy rằng sort của Perl là hoàn toàn

tổng quát và có thể thực hiện bất kì thứ tự sắp xếp nào.

Để định nghĩa việc sắp xếp theo mầu sắc khác, bạn

cần định nghĩa một trình so sánh mà mô tả cho cách so

sánh hai phần tử. Tại sao điều này lại cần thiết? Thế này,

nếu bạn nghĩ về nó, sắp xếp là việc đặt một bộ các thứ

theo một trật tự so sánh tất cả chúng với nhau. Vì bạn

không thể nào so sánh chúng ngay một lúc nên bạn cần

so sánh hai phần tử mỗi lúc, để cuối cùng dùng cái bạn

phát hiện ra về thứ tự cho từng cặp mà đặt toàn bộ cả lũ

vào hàng.

Trình so sánh được định nghĩa như một trình thông

thường. Trình này sẽ được gọi đi gọi lại, mỗi lần lại

truyền hai phần tử của danh sách cần được sắp. Trình

này phải xác định liệu giá trị thứ nhất là bé hơn, bằng

hay lớn hơn giá trị thứ hai, và cho lại một giá trị mã hoá

(sẽ được mô tả ngay sau đây). Tiến trình này được lặp lại

cho tới khi danh sách được sắp hoàn toàn.

Để tiết kiệm tốc độ thực hiện một chút, hai giá trị

này không được truyền trong mảng, mà thay vì thế được

trao cho một trình con như giá trị của biến toàn cục $a và

$b. (Bạn đừng lo - giá trị nguyên thuỷ của $a và $b được

bảo vệ an toàn.) Trình này nên cho lại một giá trị âm nào

đó nếu $a “bé hơn’ $b, bằng không nếu $a “bằng” $b, và

bất kì số dương nào nếu $a “lớn hơn” $b. Bây giờ nhớ,

“bé hơn” là tương ứng với ý nghĩa của bạn về “bé hơn” -

nó có thể là một so sánh số, tương ứng với kí tự thứ ba

Page 268: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

256

của xâu, hay thậm chí tương ứng với giá trị của bất kì

mảng nào có dùng các giá trị được truyền vào như khoá.

Đấy mới thực sự là mềm dẻo.

Sau đây là một thí dụ về một chương trình con sắp

xếp mà sẽ sắp mọi thứ theo thứ tự số:

sub by_number { if ($a < $b) { -1; } elsif ($a == $b) { 0; } elsif ($a > $b) { 1; } }

chú ý đến tên by_number. Không có gì đặc biệt về

cái tên của trình con này, nhưng bạn sẽ thấy tại sao tôi

lại thích cái tên bắt đầu bởi by_ trong giây lát.

Nhìn qua trình con này. Nếu giá trị của $a là bé hơn

(về mặt số trong trường hợp này) giá trị của $b, thì cho

lại giá trị -1. Nếu các giá trị là bằng nhau về con số, cho

lại không, còn ngoài ra cho lại 1. Vậy, theo mô tả của

cho trình so sánh sắp xếp, điều này sẽ làm việc.

Làm sao dùng được nó? Thử sắp xếp danh sách sau.

$somelist = (1,2,4,8,16,32,64,128,256);

Nếu dùng sort thông thường không sang sửa lại danh

sách, sẽ được các số sắp như chúng là các xâu, và theo

trật tự ASCII, tựa như:

@wronglist = sort @somelist; # @wronglist bây giờ là (1,128,16,2,256,32,64,8)

Chắc chắn đấy không phải là sắp xếp số. Thôi được,

Page 269: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

257

sẽ cho sort một trình sắp xếp được định nghĩa mới. Tên

của trình sắp xếp đi ngay sau từ khoá sort, như:

@rightlist = sort by_number @wronglist; # @rightlist bây giờ là (1,2,4,8,32,64,128,256)

Đây quả là mẹo. Chú ý rằng bạn có thể đọc sort với

trình con sắp xếp đi kèm theo kiểu con người: “sắp xếp

theo số”. Đó là lí do tại sao tôi đặt tên cho trình con với

tiền tố by_.

Nếu bạn cũng thiên về như thế và muốn nhấn mạnh

rằng cái đi sau sort là một trình con, bạn có thể đặt trước

nó bằng một dấu và (&), khi đó Perl sẽ không bận tâm

nữa, nhưng nó đã biết rằng cái nằm giữa từ khoá sort và

danh sách phải là một tên trình con.

Cái loại giá trị ba ngả kiểu -1, 0, 1 dựa trên cơ sở so

sánh số thường xuất hiện trong trình con so sánh đến

mức Perl có một toán tử đặc biệt để làm điều này trong

một lần. nó thường được gọi là toán tử tầu vũ trụ, và

trông giống <=>. Dùng toán tử tầu vũ trụ này, trình con

sắp xếp trên đây có thể được thay thế bằng:

sub by_number { $a <=> $b; }

Chú ý đến tầu vũ trụ giữa hai biến này. Quả vậy, nó

thực là toán tử dài ba kí tự. Tầu vũ trụ cho lại cùng giá trị

như dây chuyền if/elsif trong định nghĩa trước của trình

này. Bây giờ điều này là có ngắn gọn, nhưng bạn có thể

viết tắt lời gọi sắp xếp thêm nữa, bằng việc thay thế tên

của trình sắp xếp bằng toàn bộ trình sắp xếp trong dòng,

giống như:

@rightlist = sort { $a <=> $b } @wronglist;

Page 270: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

258

Một số người biện minh rằng điều này làm giảm tính

dễ đọc. Một số khác, lại biện minh rằng nó loại bỏ nhu

cầu phải đi đâu đó để tìm ra định nghĩa. Perl chẳng quan

tâm đến điều đó. Qui tắc cá nhân của tôi là ở chỗ nếu nó

không khớp trên một dòng hay tôi phải dùng nó nhiều

lần, nó nên thành một trình con.

Toán tử tầu vũ trụ dành cho so sánh số, có toán tử

xâu so sánh gọi là cmp. Toán tử cmp cho lại một trong ba

giá trị tuỳ theo việc so sánh xâu tương đối của hai đối.

Cho nên, một cách khác để viết trật tự mặc định là:

@result = sort { $a cmp $b } @somelist;

Có lẽ bạn còn chưa hề viết trình con đích xác này

(bắt chước sắp xếp mặc định có sẵn), chừng nào bạn còn

chưa viết một cuốn sách về Perl. Tuy thế, toán tử cmp

vẫn có ích lợi của nó, trong việc xây dựng các lược đồ

sắp thứ tự theo tầng. Chẳng hạn, bạn không cần đặt các

phần tử theo thứ tự số chừng nào chúng không bằng

nhau về mặt số, và trong trường hợp đó chúng phải được

sắp theo trật tự ASCII. (Theo mặc định, trình by_number

trên sẽ chỉ dùng cho các xâu phi số theo một trật tự ngẫu

nhiên nào đó vì không có trật tự số khi so sánh hai giá trị

không.) Sau đây là một cách nói “cần so sánh theo số,

chừng nào chúng chưa bằng nhau về số, còn, so sánh

theo xâu”:

sub by_mostly_number { ($a <=> $b) || ($a cmp $b); }

Điều này làm việc thế nào? Thế này, nếu kết quả của

tầu vũ trụ là -1 hay 1, phần còn lại của biểu thức bị bỏ

qua, và giá trị -1 hay 1 được cho lại. Nếu tầu vũ trụ tính

Page 271: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

259

ra giá trị không, toán tử cmp trở thành quan trọng, cho lại

một giá trị thứ tự thích hợp xem như giá trị của xâu.

Giá trị được so sánh không nhất thiết là giá trị được

truyền vào. Chẳng hạn, bạn có một mảng kết hợp trong

đó khoá là tên đăng nhập và các giá trị là tên thật của

từng người dùng. Giả sử bạn muốn in ra một biểu đồ

trong đó tên đăng nhập và tên thật được cất giữ theo thứ

tự tên thật. Bạn sẽ làm điều đó như thế nào?

Thực tại, việc ấy khá dễ dàng. Giả sử các giá trị là

trong mảng %names. Tên đăng nhập vậy là danh sách

của key(%names). Điều muốn đạt tới là một danh sách

các tên đăng nhập được sắp theo giá trị tương ứng, vậy

với bất kì khoá riêng $a nào, cần nhìn vào $names{$a}

và sắp xếp dựa trên điều đó. Nếu bạn nghĩ về điều này

theo cách đó, nó gần như đã tự viết ra rồi, như trong:

@sortedkeys = sort by_names keys(%names);

sub by_names { $names{$a} cmp $names{$b}; }

foreach (@sortedkeys) { print “$_ có tên thật là $names{$_}\n”; }

Với điều này tôi cũng đã thêm vào một so sánh. Giả

sử tên thật của hai người dùng là trùng nhau. Vì bản chất

chợt nẩy ra của trình sort, tôi có thể lấy một giá trị này

trước giá trị kia lần đầu tiên rồi các giá trị theo thứ tự

đảo lại cho lần sau. Điều này không tốt nếu báo cáo có

thể được nạp vào chương trình so sánh cho việc báo cáo,

cho nên tôi phải rất cố gắng tránh những thứ như vậy.

Với toán tử cmp, thật dễ dàng:

Page 272: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

260

sub by_names { ($names{$a} cmp $names{$b}) || ($a cmp $b); }

Tại đây, nếu tên thực là giống nhau, tôi sắp xếp dựa

trên tên đăng nhập. Vì tên đăng nhập được đảm bảo là

duy nhất (sau rốt, chúng là các khoá của mảng kết hợp

này, và không có hai khoá nào là như nhau), nên tôi có

thể đảm bảo một trật tự duy nhất và lặp lại được. Việc

lập trình phòng ngự tốt trong những ngày này, tốt hơn là

một cú điện thoại gọi đêm của thao tác viên để hỏi làm

sao tắt đi còi báo động.

Chuyển tự

Khi bạn muốn lấy một xâu và thay thế mọi thể

nghiệm của một kí tự nào đó bằng một kí tự mới, hay

xoá mọi thể nghiệm của một kí tự nào đó, bạn có thể đã

làm điều đó với việc chọn lựa cẩn thận chỉ lệnh s///.

Nhưng giả sử bạn phải thay đổi tất cả các a thành b và tất

cả các b thành a, sao? Bạn không thể làm điều đó với hai

chỉ lệnh s/// vì chỉ lệnh thứ hai sẽ hoàn tác lại tất cả

những thay đổi do chỉ lệnh thứ nhất thực hiện.

Tuy nhiên từ vỏ, một phép chuyển đổi dữ liệu như

vậy là đơn giản - chỉ cần dùng chỉ lệnh tr chuẩn:

tr ab ba < inda >outdata

(Nếu bạn không biết gì về chỉ lệnh tr, xin xem tài

liệu - đó là một công cụ có ích cho túi các mẹo của bạn.)

Tương tự, Perl cung cấp một toán tử tr làm việc rất giống

như thế:

tr/ab/ba/;

Page 273: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

261

Toán tử tr nhận hai đối : xâu cũ và xâu mới. Các đối

này làm việc giống như hai đối của s///: nói cách khác, có

một định biên nào đó xuất hiện ngay sau từ khoá tr làm

tách biệt và kết thúc hai đối (trong trường hợp này, một

sổ chéo, nhưng gần như kí tự nào cũng được).

Các đối cho toán tử tr giống hệt như các đối của chỉ

lệnh tr. Toán tử tr sửa đổi nội dung của biến $_ (giống

như s///), tìm các kí tự của xâu cũ bên trong biến $_. Tất

cả các kí tự như thế được tìm thấy đã được thay thế bằng

kí tự tương ứng trong xâu mới. Sau đây là một số thí dụ:

$_ = “fred and barney”; tr/fb/bf/; # $_ bây giờ là “bred and farney” tr/abcde/ABCDE/; # $_ bây giờ là “BrED AnD fArnEy” tr/a-z/A-Z/; # $_ bây giờ là “BRED AND FARNEY”

Lưu ý đến phạm vi các kí tự có thể được chỉ ra bằng

hai kí tự được phân cách bởi sổ chéo. Nếu bạn cần một

sổ chéo hằng kí tự trong xâu, đặt trước nó một sổ chéo

ngược.

Nếu xâu mới ngắn hơn xâu cũ, kí tự cuối cùng của

xâu mới sẽ được lặp lại đủ số lần để làm cho các xâu có

chiều dài như nhau, giống như:

$_ = “fred and barney”; tr/a-z/ABCDE/d; # $_ bây giờ là “ED AD BAE”

Lưu ý đến cách thức mọi chữ sau e đã biến mất vì

không có kí tự tương ứng trong danh sách mới và rằng

các dấu cách không bị tác động vì chúng không xuất

hiện trong danh sách cũ. Điều này là tương tự với thao

tác của tuỳ chọn -d của chỉ lệnh tr.

Nếu danh sách mới là rỗng và nếu không có tuỳ

chọn d, danh sách mới là giống hệt danh sách cũ. Điều

Page 274: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

262

này có vẻ hơi ngu, vì tại sao phải thay thế I cho I và 2

cho 2, nhưng thực tế, nó cũng làm đôi điều ích lợi đấy.

Giá trị cho lại của toán tử tr/// là số các kí tự được sánh

theo xâu cũ, và bằng việc thay đổi các kí tự trong chính

chúng, bạn có thể nhận được số các loại kí tự đó bên

trong xâu. Chẳng hạn:

$_ = “fred and barney”; $count = tr/a-z//; # $_ không thay đổi nhưng $count là 13 $count2 = tr/a-z/A-Z/; # $_ là chữ hoa còn $count2 là 13

Nếu bạn thêm c vào cuối (giống như viết thêm d),

điều đó có nghĩa là làm đầy đủ xâu cũ đối với tất cả 256

kí tự. Bất kì kí tự nào bạn liệt kê trong xâu cũ đã bị loại

bỏ khỏi tập tất cả các kí tự có thể; các kí tự còn lại, được

lấy theo dẫy từ thấp đến cao, từ xâu cũ. Vậy, một cách

để đếm hay thay đổi các kí tự phi số trong xâu của chúng

có thể là:

$_ = “fred and barney”; $count = tr/a-z//c; # $_ không đổi, nhưng $count là 2 tr/a-z/_/c; # $_ bây giờ là “fred_and_barney” (phi chữ =>

_) tr/a-z//cd; # $_ bây giờ là “fredandbarney” (xoá kí tự khác

chữ)

Chú ý rằng các tuỳ chọn có thể được tổ hợp, như

được trình bầy trong thí dụ cuối, nơi chúng trước hết bổ

sung cho tập hợp (danh sách các chữ trở thành danh sách

của tất cả các phi chữ) rồi dùng tuỳ chọn d để xoá bất kì

kí tự nào trong tập đó.

Tuỳ chọn cuối cùng cho tr/// là s, mà sẽ làm cứng lại

nhiều bản sao liên tiếp của cùng chữ đã được dịch vào

một bản sao. Xem như một thí dụ nhìn vào điều này:

$_ = “aaabbcccdefghi”;

Page 275: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

263

tr/defghi/abcdd/s; # $_ bây giờ là “aaabbcccabcd”

Chú ý rằng def trở thành abc, còn ghi (mà đáng trở

thành ddd nếu không có tuỳ chọn s) lại trở thành một d.

Bạn cũng để ý rằng các chữ liên tiếp tại phần đầu của

xâu, không bị đóng cứng lại vì chúng không là kết quả

của việc dịch. Sau đây là thêm một số thí dụ nữa:

$_ = “fred and barney, wilma and betty”; tr/a-z/X/s; # $_ bây giờ là “X X X, X X X” $_ = “fred and barney, wilma and betty”; tr/a-z/_/cs; # $_ bây giờ là

“fred_and_barney_wilma_and_betty”

Trong thí dụ thứ nhất, mỗi từ (chữ liên tiếp) đã bị

đóng cứng lại chỉ một chữ X. Trong thí dụ thứ hai, tất cả

chùm các phi chữ liên tiếp trở thành dấu gạch thấp duy

nhất.

Giống như s///, toán tử tr có thể hướng đích vào một

xâu khác bên cạnh $_ bằng việc dùng toán tử =~:

$name = “fred and barney”;

$name =~ tr/aeiou/X/; # $name bây giờ là “frXd Xnd bXrnXy”

Bài tập

Xem Phụ lục A về lời giải

1. Viết một chương trình để đọc một danh sách tên tệp,

bẻ mỗi tên thành phần đầu và đuôi. (Mọi thứ cho tới

dấu sổ chéo cuối cùng đã là đầu, còn mọi thứ sau dấu

sổ chéo cuối cùng là đuôi. Nếu không có sổ chéo, nó

tất cả là đuôi.) thử với những thứ như /fred, barney

Page 276: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

264

và fred/barney. Kết quả có nghĩa gì không?

2. Viết một chương trình để đọc vào một danh sách các

số rồi sắp xếp chúng theo thứ tự số, in ra danh sách

kết quả theo cột thẳng lề phải.

3. Viết một chương trình in ra các tên thực và tên đăng

nhập của người dùng trong tệp /etc/passwd, được sắp

theo tên cuối của từng người dùng. Giải pháp của

bạn có làm việc nếu hai người có cùng tên cuối

không?

4. Tạo ra một tệp bao gồm các câu, mỗi câu một dòng.

Viết một chương trình làm cho mỗi kí tự đầu câu trở

thành chữ hoa, còn phần còn lại của câu thành chữ

thường. (Liệu nó có làm việc cả khi kí tự đầu tiên

không phải là một chữ không? Bạn làm điều này thế

nào nếu câu không trên một dòng?)

Page 277: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

265

16

Truy nhập

cơ sở dữ liệu hệ thống

Lấy mật hiệu và thông tin nhóm

Thông tin mà hệ thống giữ về tên người dùng và

userID là khá công cộng. Thực ra, gần đủ mọi thứ nhưng

mật hiệu đã mật mã rồi, có sẵn cho việc nghiên cứu kĩ

hơn cho bất kì chương trình nào dám quét vào tệp

/etc/password. Tệp này có dạng thức đặc biệt - được xác

định trong passwd(5), có dạng giống như thế này:

name:passwd:uid:gid:gcos:dir:shell

Các trường được định nghĩa như sau:

name Tên đăng nhập của người dùng

passwd Mật hiệu đã mật mã hoá

Trong chương này:

Lấy mật hiệu và thông tin nhóm

Đóng và mở gói dư liệu nhị phân

Lấy thông tin mạng

Lấy thông tin khác

Page 278: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

266

uid Số hiệu uerID (0 cho root, khác không

cho người dùng bình thường)

gid Nhóm đăng nhập mặc định (nhóm 0 có

thể có quyền riêng, nhưng không nhất

thiết)

gcos Trường GCOS, điển hình có chứa tên đầy

đủ của người dùng, tiếp theo đó là dấu

phẩy và thông tin nào đó khác

dir Danh mục nhà (nơi bạn tới khi bạn gõ cd

không có đối, và nơi phần lớn các “tệp

chấm” của bạn được lưu giữ

shell Lớp vỏ đăng nhập của bạn, điển hình là

/bin/dh hay /bin/csh (hay có thể

/usr/bin/perl nếu bạn là người quái chiêu)

Phần điển hình của tệp mật hiệu trông giống thế này:

fred:*:123:15:Fred Flintstone, , , :/home/fred:/bin/csh barney:*:125:15:Barney

Rubble, , , :/home/barney:/bin/csh

Bây giờ, Perl đủ công cụ để nhận dạng loại dòng này

một cách dễ dàng (bằng việc dùng split, chẳng hạn), mà

không đưa ra một trình chuyên dụng nào. Nhưng thư

viện lập trình UNIX lại có một tập các trình đặc biệt:

getpwent, getpwuid, getpwnam, vân vân. Những trình

này là có sẵn trong Perl bằng việc dùng cùng tên và đối

tương tự và cho lại các giá trị.

Chẳng hạn, trình getpwnam() trở thành toán tử

getpwnam() của Perl. Đối duy nhất là tên người dùng

(như fred hay barney) và giá trị cho lại là dòng

/etc/passwd chia ra thành một mảng với các giá trị sau:

Page 279: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

267

($name, $passwd, $uid, $gid, #quota, $comment, $gcos, $dir, $shell)

Lưu ý rằng có thêm vài giá trị nữa ở đây so với tệp

mật hiệu. Trường $quote bao giờ cũng trống, và trường

$comment và $gcos cả hai đã chứa toàn bộ trường GCOS

(dẫu sao, cũng trên mọi hệ thống UNIX tôi đã chơi). Cho

nên, với anh bạn cũ freud, bạn được:

(“fred”, “*”, 123, 16, “”, “Fred Flintstone,,,”, “Fred Flintstone,,,”, “/home/fred”, “ /bin/csh”)

bằng việc gọi một trong hai lời gọi sau:

getpwuid(123)

getpwnam(“fred”)

Chú ý rằng getpwwui() nhận số hiệu UID, trong khi

getpwman() nhận tên đăng nhập làm đối của nó.

Bạn có lẽ muốn lấy phần này ra, dùng một số phép

toán danh sách mà chúng đã thấy trước đây. Một cách là

lấy một phần của danh sách này bằng việc dùng một lát

cắt mảng, như lấy danh mục nhà cho Fred, dùng:

($fred_home) = (getpwnam(“fred”)) [7] ; # đặt nhà của Fred

Bạn có thể quét qua toàn bộ tệp mật hiệu như thế

nào? Được, bạn có thể làm điều gì đó như:

foreach $i (1..10000) { @stuff = getpwui($i) ; } ### không khuyến cáo!

Nhưng đây có lẽ là cách làm sai. Chỉ bởi vì có nhiều

cách để làm điều đó không có nghĩa là tất cả các cách đã

đầm chắc như nhau.

Bạn có thể nghĩ về các toán tử getpwuid() và

Page 280: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

268

getpwnam() như việc truy nhập ngẫu nhiên - chúng lấy

một phần tử đặc biệt theo khoá, cho nên bạn phải có một

khoá để bắt đầu. Một cách khác để truy nhập vào tệp mật

hiệu là truy nhập tuần tự - nắm lấy mỗi phần tử theo một

trật tự ngẫu nhiên nào đó

Các trình truy nhập ngẫu nhiên vào tệp mật hiệu là

các toán tử setpwent(), getpwent(), và endpwent(). Cùng

nhau, ba toán tử này tạo thành việc chuyển tuần tự tất cả

các giá trị trong tệp mật hiệu. Toán tử setpwent() khởi

đầu việc quét từ đầu. Sau khi khởi đầu, mỗi lời gọi tới

getpwent() cho lại phần tử tiếp trong tệp mật hiệu. Khi

không còn dữ liệu để xử lí, trình getpwent() cho lại danh

sách rỗng. Cuối cùng, việc gọi endpwent() giải phóng tài

nguyên do nơi quét sử dụng - điều này được thực hiện

một cách tự động khi ra khỏi chương trình.

Mô tả này cần thí dụ, cho nên đây là một thí dụ:

setpwent ; # khởi đầu việc quét while (@list = getpwent) { # lấy phần tử tiếp ($login, $home) = @list[0, 7] ; # lấy tên đăng nhập và

nhà print “Danh mục nhà cho $login là $home\n” ; } endpwent; # làm xong tất

Mảnh thí dụ này chỉ ra danh mục nhà cho mọi người

trong tệp mật hiệu. Giả sử bạn muốn sắp xếp chúng theo

trật tự chữ cái, sao? Được, chúng ta đã học về sort trong

chương trước, cho nên dùng nó:

setpwent ; # khởi đầu việc quét while (@list = getpwent) { # lấy phần tử tiếp ($login, $home) = @list[0, 7] ; # lấy tên đăng nhập và

nhà $home{$login} = $home; # cất nó đi

Page 281: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

269

} endpwent ; # làm xong tất @keys = sort { $home{$a} cmp $home{$b} } key %home; foreach $login (@keys) { # bước qua các tên đã sắp xếp print “nhà của $login là $home{$login}\n” ; }

Đoạn chương trình này, trong khi dài hơn chút ít, lại

minh hoạ cho một vấn đề quan trọng về quét tuần tự qua

tệp mật hiệu - bạn có thể cất giữ các phần thích hợp của

dữ liệu trong cấu trúc dữ liệu bạn chọn. Phần thứ nhất

của thí dụ này quét qua toàn bộ tệp mật hiệu, tạo ra một

mảng kết hợp với khoá là tên đăng nhập và giá trị là

danh mục nhà tương ứng cho tên đăng nhập đó. Dòng

sort lấy khoá của mảng và sắp xếp chúng tương ứng với

xâu giá trị. Chu trình cuối cùng bước qua các khoá đã

sắp xếp, lần lượt in ra từng giá trị.

Nói chung, bạn nên dùng các trình truy nhập ngẫu

nhiên (getpwuid() và getpwnam() ) khi bạn tìm chỉ vài

giá trị. Với việc tìm kiếm vài giá trị hay thậm chí tìm

kiếm vét cạn, nói chung bước truy nhập tuần tự (dùng

setpwent(), getpwent(), và endpwent()) và trích các giá

trị đặc biệt mà bạn tìm để đưa vào mảng kết hợp, dễ

dàng hơn.

Tệp /etc/group được truy nhập theo cách tương tự.

Việc truy nhập tuần tự đươc cung cấp bằng các lời gọi

setgrent(), getgrent() và endgrrent(). Lời gọi getgrent() cho

lại giá trị có dạng:

($name, $passwd, $gid, $member)

Bốn giá trị này tương ứng đúng với bốn trường của

tệp /etc/group cho nên xem các mô tả trong tài liệu tham

chiếu về dạng thức tệp này về chi tiết. Các hàm truy

Page 282: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

270

nhập ngẫu nhiên tương ứng là getgrgid() (theo ID nhóm)

và getgrnam() (theo tên nhóm).

Gói và mở dữ liệu nhị phân

Mật hiệu và thông tin nhóm được biểu diễn khá đẹp

theo dạng văn bản. Các cơ sở dữ liệu hệ thống khác được

biểu diễn tự nhiên hơn dưới các dạng khác. Chẳng hạn,

địa chỉ IP của một giao diện được quản lí nội bộ như một

số bốn byte. Trong khi nó thường được giải mã thành

biểu diễn văn bản bao gồm bốn số nguyên nhỏ tách nhau

bởi dấu chấm, việc mã hoá và giải mã này lại là nỗ lực bị

phí phạm nếu con người trong khi ấy không diễn giải dữ

liệu này.

Bởi điều này, các trình mạng trong Perl mà có trông

đợi hay trả lại một địa chỉ IP thường dùng xâu bốn byte

có chứa một kí tự ch từng byte tuần tự trong bộ nhớ.

Trong khi việc xây dựng và diễn giải xâu byte như vậy là

khá trực tiếp bằng việc dùng sprintf() và ord() (không

được trình bầy ở đây), Perl cung cấp một lối tắt có thể áp

dụng được tương đương cho nhiều cấu trúc khó hơn.

Toán tử pack() làm việc có đôi chút như sprintf(),

nhận một xâu điều khiển dạng thức và một danh sách các

giá trị, rồi tạo ra một xâu từ những giá trị này. Tuy nhiên

xâu dạng thức pack được khớp với việc tạo ra một cấu

trúc dữ liệu nhị phân. Chẳng hạn, để lấy bốn số nguyên

nhỏ và gói chúng theo bốn byte liên tiếp trong một xâu

hợp thành:

$buf = pack (“CCCC”, 140, 186, 65, 25) ;

Page 283: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

271

Tại đây, xâu dạng thức pack là bốn C. Mỗi C biểu thị

cho một giá trị tách biệt được lấy từ danh sách sau

(tương tự như cái mà một trường % thực hiện trong

sprintf). Dạng thức C (tương ứng với tài liệu Perl, bìa

tham chiếu, Sách con lạc đà, tệp texinfo, hay thậm chí

cuốn Perl: The Motion Picture) nói tới một byte được

tính từ một giá trị kí tự không dấu (một số nguyên nhỏ).

Xâu kết quả trong $buf là xâu bốn kí tự - từng kí tự đã là

một byte có các giá trị 140, 186, 65 và 25.

Tương tự, dạng thức gói l sinh ra một giá trị dài có

dấu. Trên nhiều máy, đây là một số bốn byte, mặc dầu

dạng thức này là phụ thuộc máy. Trên máy “dài” bốn

byte, câu lệnh,

$buf = pack (“1”, 0x41424344) ;

sinh ra một xâu bốn kí tự trông như hoặc ABCD

hoặc DCBA, tuỳ theo liệu máy kết thúc ở đầu cuối bé

hay đầu cuối lớn (hay một cái gì đó hoàn toàn khác nếu

máy không nói theo ASCII). Điều này xảy ra bởi vì

chúng đang gói một giá trị vào thành bốn kí tự (chiều dài

của số nguyên dài), và một giá trị ngẫu nhiên được hợp

thành từ các byte biểu thị cho các giá trị ASCII cho bốn

chữ cái đầu tiên trong bảng chữ. Tương tự:

$buf = pack ( “11”, 0x41424344, 0x45464748) ;

tạp ra một xâu tám byte bao gồm ABCDEFGH hay

DCBAHGFE, một lần nữa tùy thuộc vào liệu máy là kết

thúc ở đầu cuối bé hay đầu cuối lớn.

Danh sách chính xác cho các loại dạng thức gói

khác nhau được cho trong tài liệu tham chiếu (tài liệu

ngôn ngữ Perl, hay sách con lạc đà, hay bất kì sách nào

bạn đang dùng để học về các viên ngọc khác của Perl).

Page 284: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

272

Bạn sẽ thấy vài thí dụ ở đây, nhưng tôi không định lập

danh sách cho chúng.

Điều gì xảy ra nếu bạn được cho một xâu tám byte

ABCDEFGH và được bảo rằng nó thực sự là ảnh bộ nhớ

(một kí tự là một byte) của hai giá trị dài có dấu (bốn

byte)? Bạn sẽ diễn giải nó thế nào? Nào, bạn cần làm

ngược lại pack(), gọi là unpack(). Toán tử này lấy một xâu

điều khiển dạng thức (thường đồng nhất với xâu điều

khiển dạng thức mà bạn đã cho trong pack()) và một xâu

dữ liệu, rồi cho lại một danh sách các giá trị tạo nên ảnh

bộ nhớ được xác định trong xâu dữ liệu. Chẳng hạn, lấy

xâu đó từ:

($val1, $val2) = unpack (“11”, “abcdefgh”) ;

Điều này cho lại cái gì đó tựa như 0x61626364 cho

$val1 hay có thể 0x64636261 (tuỳ theo đầu cuối lớn hay

bé). Thực ra, với giá trị cho lại, chúng có thể xác định

liệu chúng ở trên máy đầu cuối bé hay lớn.

Khoảng trắng trong xâu điều khiển dạng thức bị bỏ

qua, và có thể được dùng để làm tăng tính dễ đọc. Một

số trong xâu điều khiển dạng thức nói chung lặp lại đặc

tả trước đó nhiều lần. Chẳng hạn, CCCC cũng có thể

được viết là C4 hay C2C2 mà không làm thay đổi ý

nghĩa. (Một vài đặc tả dùng số đi sau như một phần của

đặc tả, và do vậy không thể được xem lại bội như thế

này.)

Một kí tự dạng thức cũng có thể được phép có theo

sau là dấu *, mà sẽ làm gấp bội kí tự dạng thức theo đủ

số lần để nuốt hết phần còn lại của danh sách hay phần

còn lại của xâu ảnh nhị phân (tuỳ theo liệu bạn đang gói

lại hay mở gói). Do đó, một cách khác để gói bốn kí tự

Page 285: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

273

không dấu thành một xâu là:

$buf = pack (“C*”, 140, 186, 65,25) ;

Bốn giá trị ở đây được nuốt hết bởi một đặc tả dạng

thức. Nếu bạn muốn hai số nguyên ngắn theo sau bởi

“nhiều nhất các kí tự không dấu có thể được,” bạn có thể

nói điều gì đó như:

$buf = pack ( “s2 C*”, 3241, 5826, 5, 3 , 5, 8, 9, 7, 9, 3, 2) ;

Tại đây, chúng lấy hai giá trị đầu là số nguyên ngắn

(có thể sinh ra bốn hay tám kí tự) và chín giá trị còn lại

là các kí tự không dấu (sinh ra chín kí tự, gần như chắc

chắn).

Đi theo chiều hướng khác, unpack với đặc tả dấu sao

có thể sinh ra một danh sách các phần tử với chiều dài

không xác định. Chẳng hạn, mở gói với C* tạo ra một

phần tử danh sách (một số) cho từng kí xâu. Do đó, câu

lệnh này:

@values = unpack (“C*”, “hello, world!\n”) ;

cho một danh sách 14 phần tử, mỗi phần tử là một kí

tự trong xâu này.

Lấy thông tin mạng

Perl hỗ trợ cho việc lập trình mạng theo một cách rất

quen thuộc với những người đã viết chương trình mạng

trong chương trình C. Thực ra, phần lớn các trình Perl

cung cấp việc truy nhập mạng đã có cùng tên và các

tham biến tương tự như chương trình C tương ứng.

Page 286: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

274

Chúng tôi không thể dạy một giáo trình đầy đủ về lập

trình mạng trong chương này, nhưng chúng ta lấy cái

nhìn vào một trong những đoạn nhiệm vụ đó để xem liệu

nó được thực hiện như thế nào trong Perl.

Một trong những điều bạn cần tìm ra là địa chỉ đi

cùng với tên, hay ngược lại. Trong C, bạn dùng trình

gethostbyname() để chuyển một tên mạng thành địa chỉ

mạng. Thế rồi bạn dùng địa chỉ này để tạo ra một mối

nối từ chương trình của bạn tới chương trình khác ở đâu

đó.

Trình Perl để dịch một tên máy chủ thành địa chỉ có

cùng tên và các tham biến tương tự như trình C, và trông

như thế này:

($name, $aliases, $addrtype, $length, @addrs) =

gethostbyname($name) ; # dạng tổng quát của gethostbyname

Tham biến cho trình này là tên máy chủ - chẳng hạn

slate.bedrock.com. Giá trị cho lại là một danh sách bốn

hay nhiều tham biến, tuỳ thuộc vào bao nhiêu địa chỉ

được liên kết với tên này. Nếu tên máy chủ không hợp

lệ, trình này cho lại danh sách rỗng.

Khi gethostbyname() thực hiện thành công, $name là

tên chính tắc, khác với tên cái vào nếu tên cái vào là một

biệt hiệu. $aliases là danh sách các tên cách nhau bằng

dấu cách mà qua đó biết tới máy chủ. $addrtype cho lại

giá trị mã hoá để chỉ ra dạng của địa chỉ. Trong trường

hợp này, với slate.bedrock.com, chúng ta có thể giả thiết

trước rằng giá trị này chỉ ra một địa chỉ IP, thông thường

được biểu thị như bốn số dưới 256, cách nhau bởi dấu

chấm. $length cho số các địa chỉ, thực tế là thông tin thừa

Page 287: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

275

vì dẫu sao bạn có thể nhìn vào chiều dài của @addrs.

Nhưng phần có ích của giá trị cho lại là @addrs. Mỗi

phần tử của mảng là một địa chỉ IP tách biệt, được cất

giữ trong một dạng thức bên trong, được giải quyết trong

Perl như một xâu bốn kí tự. Trong khi xâu bốn kí tự này

đích xác là cái mà các hàm mạng Perl khác tìm kiếm, giả

sử muốn in ra kết quả cho người dùng xem. Trong

trường hợp này, chúng ta cần chuyển đổi giá trị cho lại

thành một dạng thức người đọc được với sự trợ giúp của

hàm unpack() và việc truyền thông báo phụ. Sau đây là

một đoạn chương trình in ra một trong các địa chỉ IP của

slate.bedrock.com:

($addr) = (gethostbyname(‘slate.bedrock.com’) ) [4] ; print “Địa chỉ của slate là ”,

join (“.”, unpack(“C4”, $addr)), “\n” ;

ụnpack() nhận bốn kí tự và cho lại bốn số. Điều này

xảy ra theo thứ tự bên phải để join() gắn các dấu chấm

vào giữa từng cặp số để làm thành dạng người đọc được.

Lấy các thông tin khác

Giống như trình gethostbyname(), bạn cũng có thể

lấy thông tin về máy chủ theo địa chỉ, và thông tin mạng

theo cả tên và địa chỉ. Cũng có các trình để truy nhập

vào các danh sách giao thức mạng và danh sách dịch vụ.

Các trình này tất cả đã làm việc tương tự như cách chúng

làm việc trong chương trình C.

Xem Phụ lục B về các thí dụ việc dùng các trình này

trong ứng dụng thực.

Page 288: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

276

Bài tập

Xem câu trả lời ở Phụ lục A.

1. Viết một chương trình tạo ra bảng tương ứng userID

và tên thực tế các ô mật hiệu, rồi dùng ánh xạ đó để

cho hiện ra danh sách các tên thực thuộc về từng

nhóm trong tệp nhóm. (Danh sách của bạn có chứa

những người dùng, người có nhóm mặc định trong ô

mật hiệu nhưng không nói tường minh về cùng nhóm

đó trong ô nhóm, đúng không? Nếu không, làm sao

bạn hoàn thành được điều đó?

Page 289: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

277

17

Thao tác

cơ sở dữ liệu

người dùng

Cơ sở dữ liệu DBM và mảng DBM

Phần lớn các hệ thống UNIX đã có một thư viện

chuẩn gọi là DBM. Thư viện này cung cấp một tiện nghi

quản trị cơ sở dữ liệu đơn giản cho phép các chương

trình được cất giữ một tuyển tập các cặp khoá-giá trị

trong một cặp tệp đĩa. Những tệp này duy trì các giá trị

trong cơ sở dữ liệu giữa những lần gọi chương trình có

dùng cơ sở dữ liệu và những chương trình này có thể bổ

sung các giá trị mới, cập nhật các giá trị hiện có, hay xoá

các giá trị cũ.

Thư viện DBM, khá đơn giản, nhưng lại sẵn có, một

số chương trình hệ thống đã dùng nó cho những nhu cầu

Trong chương này:

Cơ sở dữ liệu DBM và mảng DBM

Mở và đóng mảng DBM

Dùng mảng DBM

Cơ sở dữ liệu truy nhập ngẫu nhiên chiều dài cố định

Cơ sở dữ liệu (văn bản) chiều dài biến đổi

Page 290: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

278

khá giản dị của chúng. Chẳng hạn, chương trình gửi thư

Berkeley (và các biến thể cùng suy dẫn của nó) cất giữ

cơ sở dữ liệu biệt hiệu (ánh xạ của địa chỉ thư vào nơi

nhận) như một cơ sở dữ liệu DBM. Phần mềm tin Usenet

phổ biến nhất cũng dùng cơ sở dữ liệu DBM để giữ dấu

vết các bài báo hiện tại và mới xem gần đấy.

Perl cung cấp việc truy nhập vào cùng cơ chế DBM

này thông qua một phương tiện còn thông minh hơn:

mảng kết hợp có thể được kết hợp với cơ sở dữ liệu

DBM qua một tiến trình tương tự như mở một tập. Mảng

kết hợp này (còn gọi là mảng DBM) vậy rồi được dùng

để truy nhập và sửa đổi cơ sở dữ liệu DBM. Việc tạo ra

một phần tử mới trong mảng này làm thay đổi ngay lập

tức cơ sở dữ liệu DBM. Việc xoá một phần tử sẽ xoá giá

trị khỏi cơ sở dữ liệu DBM. Và cứ như thế.

Kích cỡ, số lượng và loại khoá cùng giá trị trong cơ

sở dữ liệu DBM, có hạn chế, và một mảng DBM có cùng

những hạn chế đó. xem lidbm về chi tiết. Nói chung, nếu

bạn giữ cả khoá và giá trị xuống khoảng 1000 kí tự bất kì

hay ít hơn, có lẽ là OK.

Mở và đóng mảng DBM

Để kết hợp một cơ sở dữ liệu DBM với một mảng

DBM, dùng toán tử dbmopen() , trông như thế này:

dbmopen(%ARRAYNAME, “dbmfilename”, $mode);

Tham biến %ARRAYNAME là một mảng kết hợp

của Perl. (Nếu mảng này đã có giá trị, các giá trị đó bị bỏ

đi.) Mảng kết hợp này trở thành được nối với cơ sở dữ

liệu DBM có tên dbmfilename, thông thường được cất

Page 291: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

279

giữ trên đĩa như một cặp tệp có tên dbmfilenam.dir và

dbmfilename.pag. Bất kì tên mảng kết hợp hợp pháp nào

cũng đã có thể dùng được, mặc dầu các tên mảng chỉ

toàn chữ hoa, được dùng điển hình do sự tương tự với

tước hiệu tệp.

Tham biến $mode là một số kiểm soát các bit cho

phép của cặp tệp này nếu các tệp đó cần được tạo ra. Con

số này điển hình được xác định theo hệ tám: giá trị

thường hay dùng nhất là 0644 cho phép mọi người chỉ

đọc, còn riêng người chủ, có phép đọc-ghi. Nếu các tệp

này đã tồn tại, tham biến này không có tác dụng. Chẳng

hạn:

dbmopen(%FRED, “mydatabase”, 0644); # mở %FRED lên mydatabase

Lời gọi này kết hợp mảng kết hợp %FRED với các

tệp đĩa mydatabase.dir và mydatabase.pag trong danh

mục hiện tại. Nếu các tệp này chưa tồn tại, chúng được

tạo ra với mốt 0644.

Giá trị cho lại từ dbmopen() là đúng nếu cơ sở dữ

liệu có thể mở được hay tạo ra được, và là sai trong

trường hợp ngược lại, hệt như việc gọi open(). Nếu bạn

không muốn các tệp này được tạo ra, dùng giá trị $mode

của undef. Chẳng hạn:

dbmode (%A, “/etc/xx”, undef) || die “không mở được DBM /etc/xx”;

Trong trường hợp này, nếu tệp /etc/xx.dir và

/etc/xx.pag không thể mở được, lời gọi dbmopen() sẽ cho

lại sai, thay vì cố gắng tạo ra các tệp này.

Mảng DBM vẫn còn mở trong suốt lúc thực hiện

chương trình. Khi chương trình kết thúc, sự kết hợp sẽ

Page 292: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

280

kết thúc. Bạn cũng có thể phá vỡ sự kết hợp theo cách

thức tương tự như việc đóng tước hiệu tệp, bằng việc

dùng toán tử dbmclose():

dbmclose(%A);

Giống như close(), dbmclose() cho lại sai nếu điều gì

đó đi sai.

Dùng mảng DBM

Một khi cơ sở dữ liệu đã được mở, các tham chiếu

tới mảng DBM sẽ được ánh xạ vào các tham chiếu cơ sở

dữ liệu. Việc thay đổi hay bổ sung giá trị vào mảng này

tạo ra cho các mục tương ứng lập tức được ghi vào tệp

đĩa. Chẳng hạn, một khi %FRED được mở trong thí dụ

trước, có thể thêm vào, xoá bỏ hay truy nhập vào cơ sở

dữ liệu, giống như thế này:

$FRED{“fred”} = “bedrok”; # tạo ra (cập nhật) một phần tử

delete $FRED{“barney”); # bỏ một phần tử của csdl foreach $key (keys %FRED) { # đi qua mọi giá trị print “$key có giá trị của $FRED{$key}\n”; }

Chu trình cuối phải quét qua toàn bộ tệp đĩa hai lần:

một lần để truy nhập vào khoá, và lần thứ hai để tra cứu

các giá trị từ khoá. Nếu chúng ta quét qua một mảng

DBM, nói chung sẽ hiệu quả về đĩa hơn là dùng toán tử

each(), chỉ qua một bước:

while ( ($key, $value) = each (%FRED)) { print “$key có giá trị của $value\n”; }

Nếu bạn đang truy nhập vào cơ sở dữ liệu DBM hệ

Page 293: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

281

thống, như các cơ sở dữ liệu đã được tạo ra bởi sendmail

hay tin Usenet, bạn phải biết rằng các chương trình nói

chung đã gắn thêm cái đuôi kí tự NUL (“\0”) vào cuối

của các xâu này. Các trình thư viện DBM không cần cái

NUL này (chúng giải quyết dữ liệu nhị phân bằng cách

đếm byte chứ không phải là xâu kết thúc bằng NUL), và

do vậy NUL được cất giữ như một phần của dữ liệu. Do

đó bạn phải gắn kí tự NUL vào cuối dữ liệu của mình và

bỏ NUL khỏi cuối của giá trị cho lại để cho dữ liệu có

nghĩa. Chẳng hạn, để tra cứu merlyn trong cơ sở dữ liệu

biệt hiệu, thử một điều gì đó kiểu:

dbmopen(%ALI, “/etc/aliases”, undef) || die “Không biệt hiệu?”;

$value = $ALI{“merlyb\0”}; # lưu ý NUL được thêmvào chop($value); #loại bỏ NUL được thêm vào print “Thư của Randal được gắn đầu cho: $value\n”; # in

kết quả

Bản UNIX của bạn có thể đưa cơ sở dữ liệu biệt

hiệu vào /usr/lib thay vì /etc. Bạn phải lục lọi để tìm ra.

Cơ sở dữ liệu truy nhập ngẫu nhiên chiều dài cố định

Một dạng khác của dữ liệu bền vững là các tệp đĩa

hướng bản ghi với chiều dài cố định. Trong lược đồ này,

dữ liệu bao gồm một số các bản ghi với chiều dài như

nhau. Việc đánh số cho các bản ghi, không quan trọng

hay được xác định bởi một lược đồ định chỉ số nào đó.

Chẳng hạn, chúng ta có thể có một loạt các bản ghi

mà trong đó dữ liệu có 40 kí tự của họ, một kí tự tên

đệm, 40 kí tự tên, và rồi một số nguyên hai byte cho tuổi.

Mỗi bản ghi vậy là có chiều dài 83 byte. Nếu đọc tất cả

Page 294: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

282

dữ liệu trong cơ sở dữ liệu, phải đọc từng chùm 83 byte

cho tới khi đến cuối. Nếu muốn đi tới bản ghi thứ năm,

phải nhảy qua 4 lần 83 byte (332 byte) và đọc trực tiếp

bản ghi thứ năm.

Perl hỗ trợ cho các chương trình dùng kiểu tệp đĩa

như thế. Có đôi điều cần biết thêm bên cạnh những điều

bạn đã biết:

1. Mở tệp đĩa cho cả đọc và ghi

2. Chuyển quanh tệp này tới một vị trí bất kì

3. Lấy dữ liệu theo độ dài thay vì theo dòng mới tiếp

4. Ghi dữ liệu lên theo các khối chiều dài cố định

Toán tử open() nhận một dấu cộng bổ sung trước đặc

tả hướng vào/ra để chỉ ra rằng tệp thực sự được mở cho

cả đọc và ghi. Chẳng hạn:

open(A, “+<b”); # mở tệp b đọc/ghi (lỗi nếu không có tệp)

open(C, “+>d”); # tạo ra tệp d, với truy nhập đọc/ghi

open(E, “+>>f”); # mở hay tạo tệp f với việc truy nhập đọc/ghi

Lưu ý rằng tất cả những điều đã làm mới chỉ là bổ

sung thêm dấu cộng vào hướng vào/ra.

Một khi đã thu được việc mở tệp, cần di chuyển

quanh nó. Bạn làm điều này với toán tử seek(), cũng

nhận cùng ba tham biến như trình thư viện fseek(). Tham

biến thứ nhất là tước hiệu tệp; tham biến thứ hai là cho

khoảng chênh, được diễn giải đi kèm với tham biến thứ

ba. Thông thường, bạn muốn tham biến thứ ba là không

để cho tham biến thứ hai chọn được vị trí tuyệt đối cho

lần đọc tiếp hay ghi tiếp lên tệp. Chẳng hạn, đi tới bản

Page 295: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

283

ghi thứ năm trên tước hiệu tệp NAMES (như được mô tả

ở trên), bạn có thể làm điều này:

seek(NAMES, 4*83, 0);

Một khi con trỏ tệp đã được định vị lại, việc đưa vào

hay đưa ra tiếp sẽ bắt đầu từ đó. Với việc đưa ra, dùng

toán tử print. nhưng phải chắc chắn rằng dữ liệu bạn viết

là đúng chiều dài. Để thu được đúng chiều dài, chúng ta

có thể gọi tới toán tử pack():

print NAMES pack(“A40Â40s”, $first, $middle, $last, $age);

Bộ xác định pack() đó cho 49 kí tự đối với $first, một

kí tự cho $middle, 40 kí tự nữa cho $last và số nguyên

ngắn (2 byte) cho $age. Điều này tính thành 83 byte

chiều dài, và sẽ ghi tại vị trị tệp hiện tại đó.

Cuối cùng, chúng ta cần lấy ra một bản ghi đặc biệt.

Mặc dầu toán tử <NAMES> cho lại tất cả dữ liệu từ vị trí

hiện tại cho tới dòng mới tiếp, điều đó lại không đúng;

dữ liệu được giả thiết là chỉ trải trên 83 kí tự và có thể lại

không có dòng mới ở đúng chỗ. Thay vì thế, dùng toán

tử read(), trông và cách làm việc, hệt như lời gọi hệ thống

UNIX tương ứng:

$count = read(NAMES, $buf, 83);

Tham biến thứ nhất của read() là tước hiệu tệp.

Tham biến thứ hai là biến vô hướng giữ dữ liệu sẽ được

đọc. Tham biến thứ ba cho số byte cần đọc. Giá trị cho

lại từ read() là số byte thực tế đã đọc - điển hình là cùng

số byte được yêu cầu trừ phi tước hiệu tệp không được

mở hay bạn quá gần tới cuối tệp.

Một khi bạn đã có dữ liệu 83 kí tự, chỉ cần chặt nó

Page 296: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

284

ra thành các thành phần bằng toán tử unpac():

($first, $middle, $last, $age) = unpack(“A40A40s”, $buf);

Lưư ý rằng các xâu dạng thức cho pack và unpack là

như nhau. Phần lớn các chương trình cất giữ xâu này

trong một biến từ đầu trong chương trình, và thậm chí

còn tính chiều dài của bản ghi bằng việc dùng pack() thay

vì bỏ rải rác các hằng 83 ở mọi nơi:

$names = “A40AA40s”;

$names_length = length(pack($names)) ; # có thể là 83

Cơ sở dữ liệu (văn bản) chiều dài thay đổi

Nhiều cơ sở dữ liệu hệ thống (và có lẽ phần lớn cơ

sở dữ liệu do người dùng tạo ra) đã là các chuỗi dòng

văn bản con người đọc được,với một bản ghi trên mỗi

dòng. Chẳng hạn, tệp mật hiệu chưa một dòng cho mỗi

người trên hệ thống, và tệp máy chủ chứa một dòng cho

mỗi tên máy chủ.

Rất thường là những cơ sở dữ liệu này được cập

nhật bằng các trình soạn thảo văn bản đơn giản. Việc cập

nhật một cơ sở dữ liệu như vậy bao gồm việc đọc nó tất

cả vào một vùng trung gian (hoặc trong bộ nhớ hoặc trên

đĩa khác), thực hiện những thay đổi cần thiết, rồi hoặc

ghi kết quả ngược trở lại tệp nguyên thuỷ hoặc tạo ra

một tệp mới với cùng tên sau khi đã xoá hay đổi tên bản

cũ. Bạn có thể coi việc này như bước sao chép - dữ liệu

được sao từ cơ sở dữ liệu nguyên gốc sang một bản mới

của cơ sở dữ liệu ấy, tiến hành thay đổi trong khi sao

chép.

Perl hỗ trợ cho việc soạn thảo kiểu sao chép này trên

Page 297: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

285

các cơ sở dữ liệu hướng dòng bằng cách dùng việc soạn

thảo tại chỗ. Soạn thảo tại chỗ là việc sửa đổi cách thức

toán tử hình thoi (<>) đọc dữ liệu từ một danh sách các

tệp được xác định trong dòng lệnh. Thông thường nhất,

mốt soạn thảo này được truy nhập tới bằng việc đặt đối

dòng lệnh -i, nhưng chúng ta cũng có thể đặt lẫy cho mốt

soạn thảo tại chỗ từ bên trong một chương trình, như

được biểu thị trong thí dụ sau đây.

Để đặt lẫy cho mốt soạn thảo tại chỗ, đặt một giá trị

vào trong biến vô hướng $^I. Giá trị của biến này là quan

trọng, và sẽ được thảo luận ngay đây.

Khi toán tử hình thoi được sử dụng và $^I có một giá

trị (thay vì undef), các bước được đánh dấu ##INPLACE##

trong đoạn mã sau đây sẽ được thêm vào danh sách các

hành động ngầm định mà toán tử hình thoi nhận:

$ARGV = shift @ARGV; open(ARGV, “<$ARGV”); rename($ARGV, “$ARGV$^I”); ## INLACE ## unlink($ARGV); ## INPLACE ## open(ARGVOUT, “>$ARGV”); ## INPLACE ## select(ARGVOUT) ; ## INPLACE ##

Hiệu quả là ở chỗ việc đọc từ toán tử hình thoi lấy từ

tệp cũ, còn việc ghi lên tước hiệu tệp ngầm định, lại

chuyển sang một bản sao mới của tệp này. Tệp cũ vẫn

còn trong tệp dự phòng, mà chính là tước hiệu tệp với

phần hậu tố bằng giá trị của biến $^I. (Cũng có một chút

ảo thuật để sao chép các bit người chủ và phép dùng từ

tệp cũ sang tệp mới.) Những bước này được lặp lại mỗi

lần một tệp mới được rút ra từ mảng @ARGV.

Các giá trị điển hình cho $^I là những cái như .bak

hay ~, để tạo ra các tệp dự phòng rất giống với trình soạn

Page 298: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

286

thảo tạo ra. Một giá trị kì lạ nhưng có ích cho $^I là xâu

rỗng, “”, cái gây ra việc tệp cũ bị xoá sạch sau khi việc

soạn thảo hoàn tất. Điều không may là nếu hệ thống hay

chương trình bị hỏng trong khi thực hiện chương trình

bạn, bạn sẽ mất tất cả dữ liệu cũ, cho nên chúng tôi chỉ

khuyến cáo điều này cho những người bạo dạn, dại dột

hay tin cậy.

Sau đây là cách thay đổi việc đăng nhập của ai đó

vào /bin/sh bằng cách sửa đổi tệp mật hiệu:

@ARGV = (“/etc/passwd”); # nhồi vào toán tử hình thoi $^I = “.bak”; # ghi /etc/passwd.bak để an toàn while (<>) { # chu trình chính, mỗi lần cho một dòng của

/etc/passwd s#L[^;]*$#:/bin/sh#; # đổi vỏ thành /bin/sh print; # gửi ra ARGVOUT: bản mới /etc/passwd }

Như bạn có thể thấy, chương trình này khá đơn giản.

Thực ra, cùng chương trình này có thể được sinh ra toàn

bộ với một vài đối dòng lệnh như

perl -p -i.bak -e ‘s#:[^:] ‘*$#:/bin/sh#’ /etc/passwd

Chuyển mạch -p đóng ngoặc nhọn chương trình bạn

với chu trình while có chứa một câu lệnh print. Chuyển

mạch -i đặt một giá trị vào trong biến $^I. Chuyển mạch -

e xác định ra đối sau đây như một phần của chương trình

Perl đối với thân chu trình; và đối cuối cùng cho giá trị

khởi đầu cho @ARGV.

Các đối dòng lệnh được thảo luận rất chi tiết trong

sách con lạc đà về Perl.

Page 299: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

287

Bài tập

Xem Phụ lục A về lời giải

1. Tạo ra một chương trình để mở cơ sở dữ liệu

sendmail và in ra tất cả các mục.

2. Tạo ra hai chương trình: một đọc dữ liệu hình thoi,

chặt nó ra thành các từ, rồi cập nhật một tệp DBM có

ghi số lần xuất hiện của từng từ; và chương trình kia,

mở tệp DBN và cho hiển thị kết quả được sắp xếp

theo số đếm giảm dần. Chạy chương trình thứ nhất

trên vài tệp rồi xem liệu chương trình thứ hai có nhặt

ra số đếm đúng không.

Page 300: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

288

Page 301: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

289

18

Chuyển đổi

các ngôn ngữ khác

sang Perl

Chuyển chương trình awk sang Perl

Một trong nhiều điều dễ chịu về Perl là ở chỗ nó là

(ít nhất) siêu tập ngữ nghĩa của awk. Theo ngôn ngữ thực

hành, điều này có nghĩa là nếu bạn có thể làm điều gì đó

trong awk, bạn cũng có thể làm điều đó bằng cách nào

đó trong Perl. Tuy nhiên, Perl không tương thích về cú

pháp với awk. Chẳng hạn, biến NR (số bản ghi vào) của

awk được biểu diễn là $. trong Perl.

Nếu bạn có một chương trình awk có sẵn và muốn

nó chạy với Perl, bạn có thể thực hiện việc phiên dịch

máy móc bằng việc dùng tiện ích a2p được cung cấp

cùng với Perl. Tiện ích này chuyển cú pháp awk thành cú

pháp Perl, và với đại đa số chương trình awk, nó đã cho

Trong chương này:

Chuyển đổi chương trình awk sang Perl

Chuyển đổi chương trình sed sang Perl

Chuyển đổi chương trình Shell sang Perl

Page 302: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

290

ra một bản Perl có thể chạy được trực tiếp.

Để dùng tiện ích a2p, đặt chương trình awk của bạn

vào một tệp tách biệt, và gọi a2p với tên của tệp này như

đối của nó, hay hướng lại cái vào chuẩn của a2p cho tệp

này. Cái ra chuẩn kết quả sẽ là một chương trình Perl

hợp lệ. Chẳng hạn:

$ cat myawkprog BEGIN { sum = 0 } / llama / { sum += $2 } END { print “The llama count is “ sum } $ a2p < myawkprog > myperlprog $ perl myperlprog somefile The llama count is 15 $

Bạn cũng có thể nạp cái ra chuẩn của a2p trực tiếp

vào trong Perl, bởi vì trình thông dịch Perl có thể chấp

nhận một chương trình trên cái vào chuẩn nếu được chỉ

thị:

$ a2p < myawkprog | perl - somefile The llama count is 15 $

Một bản awk được chuyển sang Perl nói chung sẽ

thực hiện chức năng đồng nhất, thường với việc tăng

thêm về tốc độ, và chắc chắn không có giới hạn có sẵn

nào của awk về chiều dài dòng hay số đếm tham biến

hay bất kì cái gì. Một vài chương trình Perl đã chuyển

đổi thực tế có thể chạy chậm hơn - hành động tương

đương trong Perl đối với một thao tác awk đã cho có thể

không nhất thiết là chương trình Perl hiệu quả nhất nếu

so với việc lập trình từ đầu.

Bạn có thể chọn tối ưu thủ công cho chương trình

Page 303: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

291

Perl đã chuyển đổi, hay thêm chức năng mới vào bản

Perl của chương trình này. Điều này khá dễ dàng, bởi vì

chương trình Perl, dễ đọc hơn (xem xét rằng việc dịch là

tự động, điều này hoàn toàn thực hiện được.)

Một vài bản dịch, vẫn không cơ giới hoá. Chẳng

hạn, phép so sánh bé hơn cho cả hai số và xâu trong awk

đã được biểu diễn bằng toán tử <. Trong Perl, bạn có lt

cho xâu và < cho số. awk nói chung dự đoán có lí về tính

chất số hay xâu của hai giá trị được so sánh, còn tiện ích

a2p chỉ đoán đơn giản. Tuy nhiên, có thể là không biết

đủ về hai giá trị để xác định liệu phép so sánh số hay xâu

được đảm bảo, cho nên a2p đưa ra toán tử có thể nhất và

đánh dấu khả năng dòng lỗi bằng #?? (chú thích của

Perl) và một giải thích. Phải chắc duyệt qua cái ra cho

những lời chú thích như vậy sau khi chuyển đổi để kiểm

chứng lại việc đoán có đúng không.

Về chi tiết hoạt động của a2p, xin xem tài liệu.

Nếu a2p không có trong cùng danh mục mà bạn lấy

Perl, hỏi người cái đặt Perl của bạn.

Chuyển đổi chương trình sed sang Perl

Được, điều này có thể bắt đầu có vẻ như sự lặp lại,

nhưng thử đoán xem cái gì... Perl là một siêu tệp ngữ

nghĩa của sed cũng như awk.

Và đi kèm với Perl là trình dịch sed sang Perl gọi là

s2p. Như với a2p, s2p nhận một bản viết của sed trên cái

vào chuẩn và ghi ra một chương trình Perl trên cái ra

chuẩn. Không giống a2p, chương trình đã được chuyển

đổi hiếm khi hành xử sai, cho nên bạn có thể tin cậy hơn

Page 304: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

292

vào việc nó làm việc, chắn lỗi trong s2p hay Perl.

Chương trình sed đã chuyển đổi có thể làm việc

nhanh hơn hay chậm hơn bản gốc, nhưng nói chung là

nhanh hơn nhiều (nhờ có các trình biểu thức chính qui

tối ưu của Perl).

Bản ghi sed đã chuyển đổi có thể làm việc với hay

không có tuỳ chọn -n, mang cùng ý nghĩa như khoá

tương ứng cho sed. Để làm điều này, bản ghi đã chuyển

đổi phải tự nạp nó vào trong bộ tiền xử lí C, và điều này

làm chậm lại việc bắt đầu một chút ít. Nếu bạn biết rằng

bạn bao giờ cũng sẽ gọi tới bản ghi sed đã chuyển đổi dù

có hay không tuỳ chọn -n (như khi bạn đang chuyển bản

ghi sed được dùng trong chương trình vỏ lớn hơn với các

đối đã biết), bạn có thể thông báo s2p (qua các khoá -s

và -p), và nó sẽ tối ưu bản ghi cho việc thiết đặt khoá đó.

Xem như một thí dụ về việc Perl linh hoạt và mạnh

mẽ đến đâu, trình dịch s2p được viết trong Perl. Nếu bạn

muốn thấy cách Larry lập trình trong Perl (cho dù đó là

việc lập trình rất cổ điển gần như không thay đổi từ bản

Perl 2), nhìn vào trình dịch này. Phải chắc là bạn đang

ngồi xuống xem.

Chuyển đổi chương trình Shell sang Perl

Nào. Bạn có nghĩ là có bộ dịch shell sang Perl

không?

Không. Nhiều người đã hỏi điều như thế, nhưng vấn

đề thực lại là ở chỗ phần lớn những điều mà bản ghi lớp

vỏ shell thực hiện, lại không được shell thực hiện. Phần

lớn các bản ghi shell thực ra đã dành tất cả thời gian

Page 305: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

293

trong việc gọi các chương trình tách biệt để trích ra các

mẩu của xâu, so sánh số, nối các tệp, xoá danh mục, cứ

như vậy. Việc chuyển đổi một bản ghi như vậy sang Perl

hoặc sẽ đòi hỏi việc hiểu về sự vận hành của từng tiện

ích được gọi, hoặc bỏ lại cho Perl gọi từng tiện ích này,

mà chẳng thu được gì.

Cho nên, cách tốt nhất mà bạn có thể làm là nhìn

vào bản ghi lớp vỏ, hình dung ra nó làm gì, và bắt đầu từ

đầu với Perl. Tất nhiên, bạn có thể làm việc chuyển tự

nhanh và bẩn, bằng việc đặt những phần chính của bản

ghi gốc vào bên trong các lời gọi system() hoặc dấu nháy

đơn ngược. Bạn cũng có thể thay thế một số trong các

phép toán này bằng Perl tự nhiên: chẳng hạn, thay thế

system(“rm fred”) bằng unlink(“fred”), hay một chu trình for

của lớp vỏ bằng chu trình for của Perl. Nhưng nói chung,

bạn sẽ thấy nó có một chút giống việc chuyển chương

trình COBOL vào C (với cùng việc giảm bớt số kí tự và

tăng tính khó đọc).

Bài tập

Xem trả lời ở Phụ lục A.

1. Chuyển bản ghi lớp vỏ sau đây thành chương trình

Perl:

cat /etc/passwd | awk -F : ‘ {print $1, $6 } ‘ | while read user home do newsrc=”$home/ .newsrc” if [ -r $newsrc ] then

Page 306: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

294

if grep -s ‘^comp\.lang\.perl:’ $newsrc then echo “$user is a good person, and read

comp.lang.perl!” fi fi done

Page 307: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

295

Phụ lục A

Trả lời các bài tập

Phụ lục này nêu câu trả lời cho các bài tập được cho

ở cuối mỗi chương.

Chương 2, Dữ liệu vô hướng

1. Sau đây là một cách để thực hiện nó:

$pi = 3.141592654; $result = 2 * $pi * 12.5; print “radius 12.5 is circumference $result’n” ;

Trước hết cho một giá trị hằng () cho biến vô

hướng $pi. Tiếp đó tính chu vi bằng việc dùng giá trị này

của $pi trong biểu thức. Cuối cùng in ra kết quả bằng

cách dùng một xâu có chứa một tham chiếu tới kết quả

2. Sau đây là một cách thực hiện nó:

print “What is the radius: “; chop ($radius = <STDIN>) ; $pi = 3.141592654;

Page 308: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

296

$result = 2 * $pi * $radius ; print “radius $radius is circumference $result’n” ;

Điều này tương tự với bài tập trước, nhưng ở đây

chúng ta hỏi người cho chạy chương trình này về một giá

trị, dùng câu lệnh print để nhắc, rồi dùng toán tử

<STDIN> để đọc một dòng từ thiết bị cuối.

Nếu chúng ta bỏ chop() đi, được dấu dòng mới ở

giữa xâu hiển thị tại cuối. Điều quan trọng là phải bỏ dấu

dòng mới sớm nhất có thể được.

3. Sau đây là một cách thực hiện nó:

print “First number: “; chop ($a = <STDIN>) ; print “Second number: “; chop ($b = <STDIN>) ; $c = $a * $b ; print “answer is $c.\n” ;

Dòng thứ nhất làm ba việc: nhắc bạn bằng một

thông báo, đọc một dòng từ cái vào chuẩn, rồi bỏ đi dấu

dòng mới không tránh khỏi tại cuối xâu. Lưu ý rằng vì

chúng ta đang dùng biến của $a hoàn toàn là số nên có

thể bỏ chop() ở đây, vì 45\n là 45 khi được dùng theo kiểu

số. Tuy nhiên, việc lập trình bất cẩn như thế có thể quay

lại ám ảnh chúng ta về sau (chẳng hạn, nếu chúng ta đưa

$a vào trong thông báo).

Dòng thứ hai làm cùng điều cho số thứ hai và đặt nó

vào trong biến vô hướng $b.

Dòng thứ ba nhân hai số với nhau và in ra kết quả.

Lưu ý dấu dòng mới tại cuối xâu ở đây, tương phản với

việc thiếu nó ở hai dòng đầu. Hai thông báo đầu là lời

nhắc, mà với nó cái vào của người dùng cần được đặt

trên cùng dòng. Thông báo cuối này là câu lệnh đầy đủ;

Page 309: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

297

nếu chúng ta bỏ dấu dòng mới ra khỏi xâu, lời nhắc của

lớp vỏ sẽ xuất hiện ngay sau thông báo này. Không hay

lắm.

3. Sau đây là một cách thực hiện nó:

print “String: “; $a = <STDIN> ; print “Number of times: “; chop ($b = <STDIN>) ; $c = $a * $b ; print “The result is: \n$c” ;

Giống như thí dụ trước, hai dòng đầu hỏi, và nhận,

các giá trị cho hai biến. Không giống bài tập trước,

chúng ta không vứt dấu dòng mới tại cuối xâu, bởi vì cần

nó! Dòng thứ ba lấy hai giá trị đưa vào và thực hiện việc

lặp lại xâu trên chúng, rồi hiển thị câu trả lời. Lưu ý rằng

việc xen lẫn $c là không cho phép dấu dòng mới, bởi vì

chúng ta tin rằng $c bao giờ cũng sẽ kết thúc trong một

dấu dòng mới theo bất kì cách nào.

Chương 3, Mảng và dữ liệu danh sách

1. Một cách thực hiện điều này là:

print “Enter the list of strings:\n “; @list = <STDIN> ; @reverselist = reverse (@list) ; print @reverselist ;

Dòng đầu tiên nhắc các xâu. Dòng thứ hai đọc xâu

vào một biến mảng. Dòng thứ ba tính danh sách theo thứ

tự ngược, cất giữ nó vào trong một biến khác, và dòng

cuối cùng hiển thị kết quả.

Thực tế chúng ta có thể tổ hợp ba dòng cuối, kết quả

là:

Page 310: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

298

print “Enter the list of strings:\n “; print reverse (<STDIN>) ;

Điều này làm việc bởi vì phép toán print trông đợi

một danh sách, còn reverse() cho lại một danh sách - cho

nên chúng hoà hợp. Và reverse() cần một danh sách các

giá trị để đảo ngược, còn <STDIN> trong hoàn cảnh

mảng, cho lại một danh sách các dòng, cho nên chúng

cũng hợp nữa!

2. Một cách thực hiện điều này là:

print “Enter the line number: ”; chop ($a = <STDIN>) ; print “Enter the lines, end with ^D:\n”; $b = <STDIN> ; print “Answer: $b[$a-1]” ;

Dòng thứ nhất nhắc một số, đọc nó từ cái vào chuẩn,

và bỏ dấu dòng mới phiền phức. Dòng thứ hai hỏi một

danh dách các xâu, rồi dùng toán tử <STDIN> trong

hoàn cảnh mảng để đọc tất cả các dòng cho tới cuối tệp

vào trong một biến mảng. Câu lệnh cuối cùng in ra câu

trả lời, bằng việc dùng một tham chiếu mảng để chọn ra

đúng dòng. Lưu ý rằng chúng ta không phải thêm dấu

dòng mới vào cuối xâu này, bởi vì dòng này được chọn

từ mảng @b vẫn có kết thúc là dấu dòng mới.

Nếu bạn thử điều này từ một thiết bị cuối được lập

cấu hình theo cách thông thường nhất, bạn sẽ cần gõ

Control-D tại bàn phím để chỉ ra cuối tệp.

3. Một cách thực hiện điều này là:

srand; print “List of strings: ”; $b = <STDIN> ;

Page 311: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

299

print “Answer: $b[rand(@b)]” ;

Dòng thứ nhất khởi đầu cho bộ sinh số ngẫu nhiên.

Dòng thứ hai đọc một loạt các xâu. Dòng thứ ba chọn

một phần tử ngẫu nhiên từ loạt các xâu đó và in nó ra.

Chương 4, Cấu trúc điều khiển

1. Sau đây là một cách thực hiện nó:

print “What temperature is it? ”; chop ($temperature = <STDIN>) ; if ($temperature > 72) { print “Too hot!\n” ; } else { print “Too cold!\n” ; }

Dòng đầu tiên nhắc bạn về nhiệt độ. Dòng thứ hai

chấp nhận nhiệt độ làm cái vào. Câu lệnh if trên dòng thứ

5 chọn một trong hai thông báo để in ra, tuỳ theo giá trị

của $temperature.

2. Sau đây là một cách thực hiện nó:

print “What temperature is it? ”; chop ($temperature = <STDIN>) ; if ($temperature > 75) { print “Too hot!\n” ; } elsif ($temperature < 68) { print “Too cold!\n” ; } else { print “Just right!\n” ; }

Tại đây chúng ta đã sửa chương trình này để bao

Page 312: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

300

gồm việc chọn lựa ba ngả. Trước hết, nhiệt độ được so

sánh với 75, rồi 68. Lưu ý rằng chỉ một trong ba sự chọn

lựa này sẽ được thực hiện mỗi lần qua chương trình này.

3. Sau đây là một cách thực hiện nó:

print “Enter a number (999 to quit): ”; chop ($n = <STDIN>) ; while ($n != 999) { $sum += $n ;

print “Enter another number (999 to quit): “; chop ($n = <STDIN>) ;

} print “The sum is $sum\n” ;

Dòng thứ nhất nhắc cho số thứ nhất. Dòng thứ hai

đọc số từ thiết bị cuối. Chu trình while tiếp tục thực hiện

chừng nào mà số này chưa lớn hơn 999.

Toán tử += tích luỹ các số vào trong biến $sum. Lưu

ý rằng giá trị khởi đầu của $sum là undef, một giá trị hay

cho bộ tích luỹ bởi vì giá trị thứ nhất được cộng vào sẽ là

cộng với 0 (nhớ rằng undef được dùng như số là không.)

Bên trong chu trình này, chúng ta phải nhắc và nhận

một số khác, để cho phép kiểm thử tại đỉnh của chu trình

lại là một số mới được đưa vào.

Khi chu trình được đi ra, chương trình sẽ in kết quả

đã tích luỹ.

Lưu ý rằng nếu bạn đưa vào 999 ngay, giá trị của

$sum sẽ khác không, nhưng là một xâu rỗng - giá trị của

undef khi được dùng như một xâu. Nếu bạn muốn đảm

bảo rằng chương trình in ra không trong trường hợp này,

bạn nên khởi đầu giá trị của $sum ở đầu chương trình với

Page 313: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

301

$sum = 0.

4. Sau đây là một cách thực hiện nó:

print “Enter some strings, end with ^D:\n”; $strings = <STDIN> ; while ($strings) { print pop(@strings) ; }

Trước hết chương trình này yêu cầu các xâu. Các

xâu này được cất giữ trong biến mảng @strings, mỗi xâu

một phần tử.

Biểu thức điều khiển của chu trình while là @strings.

Biểu thức điều khiển đang tìm một giá trị (true hay

false), và do đó tính biểu thức này trong hoàn cảnh vô

hướng. Tên của mảng (như @strings) khi được dùng

trong hoàn cảnh vô hướng là số các phần tử hiện đang

trong mảng. Chừng nào mà mảng còn không rỗng, số

này là khác không, và do đó đúng. Điều này là rất thông

dụng trong khẩu ngữ Perl “làm điều này khi mảng khác

rỗng.”

Thân của chu trình in ra một giá trị, thu được bởi

việc lấy ra phần tử bên phải nhất của mảng. Do vậy, mỗi

lần qua chu trình, mảng lại ngắn bớt đi một phần tử, bởi

vì phần tử đó đã được in ra.

Bạn có thể xét việc dùng chỉ số cho vấn đề này. Như

chúng tôi đã nói, có nhiều cách thực hiện nó. Tuy nhiên,

bạn hiếm khi thấy chỉ số trong các chương trình Perl

thực sự bởi vì gần như bao giờ cũng có cách khác tốt

hơn.

Page 314: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

302

5. Sau đây là một cách thực hiện nó không dùng danh

sách:

for ($number = 0 ; $number <= 32 ; $number++) { $square = $number * $number ;

print “%5g %8g\n” , $number, $square; }

Và đây là cách thực hiện nó dùng danh sách

foreach $number (0..32) { $square = $number * $number; printf “%5g %8g\n”, $number, $square ;

Các giải pháp này cả hai đã chứa chu trình, bằng

việc dùng các câu lệnh for và foreach. Thân của các chu

trình này là như nhau, bởi vì với cả hai giải pháp, giá trị

của $number đã từ 0 tới 32 cho mỗi lần lặp.

Giải pháp thứ nhất dùng câu lệnh for kiểu C truyền

thống. Ba biểu thức tương ứng: đặt $number là 0, kiểm

tra để xem liệu $number có bé hơn 32 hay không, và tăng

$number sau mỗi lần lặp.

Giải pháp thứ hai dùng câu lệnh foreach tựa lớp vỏ

C. Danh sách 33 phần tử (0 tới 32) được tạo ra, bằng

việc dùng cấu tử danh sách. Biến $number vậy được đặt

lần lượt cho mỗi phần tử.

Chương 5, Mảng kết hợp

1. Sau đây là một cách thực hiện nó:

%map = (‘red’, ‘apple’, ‘green’, ‘leaves’, ‘blue’, ‘ocean’) ; print “A string please: ”; chop ($some_string =

<STDIN>) ;

Page 315: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

303

print “The value for $some_string Ý $map{$some_string}\n”;

Dòng thứ nhất tạo ra mảng kết hợp, cho nó cặp

khoá-giá trị mong muốn. Dòng thứ hai lấy một xâu, loại

bỏ dấu dòng mới phiền phức. Dòng thứ ba in ra giá trị đã

đưa vào và giá trị được ánh xạ của nó.

Bạn cũng có thể tạo ra mảng kết hợp qua một loạt

các phép gán tách biệt, kiểu như:

$map {‘red’} = ‘apple’ ; $map {‘green’} = ‘leaves’ ; $map {‘blue’} = ‘ocean’ ;

2. Sau đây là một cách thực hiện nó:

@words = <STDIN> ; # read the words foreach $word (@words) { chop ($words) ; # remove that pesky newline $count {$word} = $count {$word} + 1 ; # or $count

{$word}++ } foreach $word (keys %count) { print “$word was seen $count {$word} times\n” ; }

Dòng đầu tiên đọc các dòng vào mảng @words. nhớ

rằng điều này sẽ làm cho từng dòng kết thúc như một

phần tử tách biệt của mảng, với kí tự dòng mới vẫn

không bị động đến.

Bốn dòng tiếp bước qua mảng, đặt $word lần lượt

bằng mỗi dòng. Dấu dòng mới được bỏ đi bằng chop(),

và rồi điều ảo thuật xảy ra. Mỗi từ được dùng như một

khoá trong mảng kết hợp. Giá trị của phần tử được chọn

bởi khoá này (word) là số đếm số lần chúng ta đã thấy từ

Page 316: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

304

đó cho tới lúc đó. Khởi đầu, không có phần tử nào trong

mảng cả, cho nên nếu từ wild được thấy lần đầu tiên,

chúng ta có $count {“wild”}, vốn là undef. Giá trị undef

cộng với một trở thành không cộng một, hay một. (Nhớ

rằng undef trông giống như không nếu được dùng như

số.) Lần tiếp chạy qua, chúng ta cộng thêm một, hay hai

vân vân.

Một cách thông thường khác để viết việc tăng lên

được cho trong phần chú thích. Người lập trình Perl

thành thạo có khuynh hướng lười nhác (chúng ta gọi

điều đó là “cô đọng”), và sẽ không bao giờ đi viết tham

chiếu cùng mảng kết hợp ở cả hai vế của phép gán khi

một phép tự tăng đơn giản cũng làm được điều đó.

Sau khi các từ đã được đếm xong, vài dòng cuối

bước qua mảng kết hợp bằng cách nhìn vào từng khoá

của nó mỗi lúc. Khoá và giá trị tương ứng được in ra sau

khi đã được xen lẫn vào trong xâu.

Câu trả lời thách thức phụ thêm trông như câu trả lời

này, với toán tử sort được chèn ngay trước từ keys trên

dòng thứ ba kể từ cuối. Không sắp xếp, cái ra kết quả

dường như là ngẫu nhiên và không thể đoán nổi. Tuy

nhiên, một khi đã được sắp xếp, cái ra là dự đoán được

và nhất quán. (Riêng cá nhân tôi, tôi thà dùng toán tử

keys mà không thêm sắp xếp vào ngay lập tức trước nó -

điều này đảm bảo rằng việc cho chạy lại trên cùng dữ

liệu hay dữ liệu tương tự sẽ sinh ra kết quả so sánh

được.)

Page 317: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

305

Chương 6, Vào/ra cơ sở

1. Sau đây là một cách thực hiện nó:

print reverse <> ;

Bạn có thể ngạc nhiên ở sự vắn tắt của câu trả lời

này, nhưng điều này sẽ làm cho mọi việc được thực hiện.

Đây là điều vẫn xảy ra, từ bên trong ra:

Trước hết, toán tử reverse tìm danh sách các đối của

nó. Điều này có nghĩa là toán tử hình thoi (<>) được tính

trong hoàn cảnh mảng. Vậy, tất cả các dòng của các tệp

có tên trong đối dòng lệnh (hoặc cái vào chuẩn, nếu

không tệp nào được nêu tên) được đọc vào, và được

nhào nặn thành danh sách với mỗi dòng một phần tử.

Tiếp đó toán tử reverse đảo ngược danh sách từ đầu

nọ sang đầu kia.

Cuối cùng toán tử print tạo ra danh sách kết quả, và

hiển thị nó.

2. Sau đây là một cách thực hiện nó:

print “List of strings:\n” ; chop (@strings = <STDIN>) ; foreach (@strings) { printf “%20s\n”, $_ ; }

Dòng đầu tiên nhắc danh sách các xâu.

Dòng tiếp đọc tất cả các xâu vào một mảng, và bỏ

dấu dòng mới tại cuối mỗi dòng.

Chu trình foreach bước qua mảng này, cho $_ giá trị

của mỗi dòng.

Page 318: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

306

Toán tử printf nhận hai đối: đối thứ nhất xác định

dạng thức - “%20s\n” có nghĩa là cột dồn phải 20 kí tự,

tiếp sau đó là dòng mới.

3. Sau đây là một cách thực hiện nó:

print “Chiều rộng trường: ” ; chop ($width = <STDIN>) ; print “List of strings:\n” ; chop(@strings = <STDIN>) ; foreach (@strings) { prìnt “%$ {width}s\n”, $_ ; }

Từ lời giải của bài tập trước, chúng ta đã thêm lời

nhắc và đáp ứng cho chiều rộng trường.

Việc thay đổi khác là ở chỗ xâu dạng thức cho printf

bây giờ có chứa một tham chiếu biến. Giá trị của $width

được đưa vào bên trong xâu trước khi printf xem xét dạng

thức này. Lưu ý rằng chúng ta không thể viết xâu này

như:

printf “%$width\n”, $_ ; # sai

vì thế, Perl sẽ tìm một biến có tên là $widths, không

phải là biến có tên $width mà gắn cho s. Cách khác để

viết điều này là:

printf “%$width”.”s\n”, $_ ; # đúng

bởi vì việc kết thúc của xâu này cũng sẽ kết thúc tên

biến, bảo vệ kí tự sau khỏi bị hút hết vào tên.

Page 319: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

307

Chương 7, Biểu thức chính qui

1. Sau đây là một số câu trả lời có thể

(a) /a+b*/

(b) /\\*\**/ (Nhớ rằng dấu sổ chéo ngược phủ định ý

nghĩa của kí tự đặc biệt đi sau.)

(c) / ($whatver) {3} / (Bạn phải có dấu ngoặc tròn,

hoặc nếu không, phép nhân sẽ áp dụng vào kí tự

cuối của $whatever; điều này cũng sai nếu

$whatever có kí tự đặc biệt.)

(d) / [\000-\377] {5} / hay / (.| \n) {5} / (Bạn không thể

dùng chấm một mình ở đây, bởi vì chấm không

sánh với dấu dòng mới.)

(e) / (^| \s) (\S+) (\s+\2)+/ (\S là không khoảng trắng,

còn \2 là tham chiếu tới bất kì cái gì mà “từ” có

thể là; dấu mũ hay thay thế khoảng trắng đảm

bảo rằng \S+ bắt đầu tại biên khoảng trắng.)

2. (a) Một cách thực hiện điều này là:

while (<STDIN>) { if (/a/i && /e/i && /i/i && /o/i && /u/i) { print ; } }

Tại đây, chúng ta có một biểu thức bao gồm năm

toán tử đối sánh. Các toán tử này tất cả đã nhòm vào nội

dung của biến $_, mà là nơi biểu thức điều khiển của chu

trình while đặt vào từng dòng. Toán tử đối sánh sẽ đúng

chỉ khi tìm thấy tất cả năm nguyên âm.

Page 320: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

308

Lưu ý rằng ngay khi không tìm thấy bất kì một trong

năm nguyên âm này, phần còn lại của biểu thức này bị

bỏ qua, bởi vì toán tử && không tính đối dúng của nó

nếu đối bên trái sai.

(b) Một cách để làm điều này là:

while (<STDIN>) { if (/a,*e.*i.*o.*u/i) { print ; } }

Câu trả lời này trở nên dễ dàng hơn phần khác của

bài tập này. Tại đây chúng ta có một biểu thức chính qui

đơn giản, tìm năm nguyên âm tuần tự, tác nhau bởi một

số dấu cách.

3. Một cách thực hiện điều này là:

while (<STDIN>) { ($user, $pass, $uid, $gid, $gcos) = split(/:/) ; ($real) = split (/,/,$gcos); print “$user is $real\n” ; }

Chu trình while ngoài đọc một dòng mỗi lúc từ tệp

dạng thức mật hiệu, vào trong biến $_, kết thúc khi

không còn dòng nào được đọc vào.

Dòng thứ nhất của thân chu trình while chia phần

dòng này ra theo dấu hai chấm, cất giữ năm giá trị đầu

vào các biến vô hướng riêng với tên có ý nghĩa.

Trường GCOS (trường thứ năm) được chặt ra theo

dấu phẩy, với danh sách kết quả được gán cho một biến

vô hướng đến được bao trong ngoặc tròn. Các ngoặc tròn

Page 321: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

309

này là quan trọng: chúng ta làm cho phép gán này thành

phép gán mảng, thay vì là phép gán logic. Biến vô hướng

$real nhận phần tử đầu của danh sách và các phần tử còn

lại là được bỏ qua.

4. Một cách thực hiện điều này là:

while (<STDIN>) { ($user, $pass, $uid, $gid, $gcos) = split(/:/) ; ($real) = split (/,/,$gcos); ($first) = split(/\s+/, $real) ; $seen {$first}++; } foreach (key %seen) { if ($seen {$_} > 1) {

print “$_ was seen $seen {$_} times\n” ; }

}

Chu trình while làm việc khá giống với cho trình

while trong bài tập trước. Bên cạnh việc chặt dòng này ra

thành các trường và trường GCOS thành tên thực (và các

phần khác), chu trình này cũng chặt tên thực thành tên

đầu (và phần còn lại). Một khi tên đầu được biết tới,

phần tử mảng kết hợp trong %seen được tăng lên, lưu ý

rằng chúng đã thấy một tên đầu tiên đặc biệt. Lưu ý rằng

chu trình này không làm việc in nào.

Chu trình foreach bước qua tất cả các khoá

của %seen (các tên đầu lấy từ tệp mật hiệu), gán cho mỗi

khoá lần lượt vào trong $_. Nếu giá trị này được cất giữ

trong %seen tại một khoá đã cho mà lớn hơn một, chúng

ta thấy cái tên đầu này nhiều lần. Câu lệnh if kiểm thử

điều này, và in ra thông báo nếu cần.

Page 322: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

310

5. Một cách thực hiện điều này là:

while (<STDIN>) { ($user, $pass, $uid, $gid, $gcos) = split(/:/) ; ($real) = split (/,/,$gcos); ($first) = split(/\s+/, $real) ; $name {$first} .= “ $user” ; } foreach (key %seen) { if ($seen {$_} =~ /. /) {

print “$_ is used by: $seen {$_} times\n” ; }

}

Chương trình này giống như câu trả lời ở bài tập

trước, nhưng thay vì đơn thuần cất giữ số đếm, chúng ta

gắn thêm tên đăng nhập của người dùng vào phần

tử %names mà có khoá cho tên đầu. Vậy với Fred Rogers

(đăng nhập mrrogers), $names {“Fred”} trở thành

“ mrrogers”, và khi Fred Flintstone (đăng nhập fred) tới,

được $names {“Fred”} là “ mrrogers fred”. Sau khi chu

trình này hoàn tất, chúng ta có một bảng tương ứng tất cả

các tên đầu cho tất cả những người dùng có chúng ta.

Chu trình foreach, giống như câu trả lời của bài tập

trước, sẽ bước qua mảng kết hợp kết quả. Tuy nhiên,

thay vì kiểm thử một giá trị phần tử mảng kết hợp với

một số lớn hơn một, bây giờ chúng ta phải xem liệu có

nhiều tên đăng nhập theo giá trị này không. Chúng ta

làm điều này bởi việc cất giữ giá trị này vào trong biến

vô hướng $names (lấy tên giống %names để cho tiện) và

xem liệu giá trị này có dấu cách theo sau bất kì kí tự nào

không. Nếu có, tên đầu sẽ được dùng chung, và thông

báo kết quả cho biết đăng nhập nào sẽ dùng chung tên

Page 323: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

311

đó.

Chương 8, Hàm

1. Sau đây là một cách để thực hiện nó

sub card { @card_map = (0, “one”, “two”, “three”, “four”, “five”, “six”, “seven”, “eight”, “nine”) ; local ($num) = @_ ; if ($card_map[$num]) { $card_map[$num] ; # return value } else { $num ; # return value } } # driver routine: while (<>) { chop ; print “card of $_ is “, &card($_), “\n” ; }

Trình &card (tên như vậy vì nó cho lại một tên số

lượng với một giá trị đã cho) bắt đầu bằng việc khởi đầu

một mảng hằng gọi là @card_map. Mảng này có các giá

trị sao cho $card_map[6] là six, làm cho nó khá dễ thực

hiện ánh xạ này.

Câu lệnh if xác định liệu giá trị này có trong phạm vi

hay không bằng việc nhìn vào số trong mảng - nếu có

một phần tử mảng tương ứng, phép thử là đúng, sao cho

phần tử mảng được cho lại. Nếu không có phần tử nào

tương ứng (như khi $num là 11 hay -4, giá trị cho lại từ

việc tra bảng là undef , cho nên nhánh else của câu lệnh if

được thực hiện, cho lại số gốc. Bạn cũng có thể thay thế

Page 324: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

312

toàn bộ câu lệnh if với một biểu thức:

$card_map {$num} || $num ;

Nếu giá trị vế bên trái của || là đúng, nó là giá trị của

toàn bộ biểu thức, mà sẽ được cho lại. Nếu nó là sai (như

khi $num ở ngoài phạm vi), vế phải của toán tử || được

tính, cho lại $num xem như giá trị cho lại.

Trình điều khiển nhận các dòng liên tiếp, cắt bỏ dấu

dòng mới của chúng ta và mỗi lúc trao chúng cho trình

&card, in ra kết quả.

2. Sau đây là một cách thực hiện nó:

sub card { ... ; } # from previous problem print “Enter first number: “ ; chop ($first = <STDIN>) ; print “Enter second number: “ ; chop ($second = <STDIN>) ; $message = &card($first) . “ plus “ . &card($second) . “ equals “ . &card($first+$second) . “.\n” ; $message =~ s/^./\u$&/ ; print $message ;

Hai câu lệnh print thứ nhất nhắc đưa vào hai số, tiếp

ngay sau đó là hai câu lệnh đọc các giá trị vào $first và

$second.

Một xâu có tên $message được xây dựng nên bằng

việc gọi &card ba lần, mỗi lần cho một giá trị, và một lần

cho tổng.

Khi thông báo này được xây dựng, kí tự thứ nhất

của nó được viết hoa lên bằng toán tử sổ chéo ngược \u.

Thông báo này tiếp đó được in ra. Bạn có thể tổ hợp hai

Page 325: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

313

câu lệnh cuối là:

print “\u$message” ;

Nhưng không may là bạn không thể tổ hợp cách xây

dựng thông báo này với việc làm thành chữ hoa cho kí tự

đầu tiên một cách dễ dàng.

3. Sau đây là một cách thực hiện nó:

sub card { @card_map = (0, “one”, “two”, “three”, “four”, “five”, “six”, “seven”, “eight”, “nine”) ; local ($num) = @_ ; if ($num < 0) { $negative = “negative” ; $num = - $num ; } if ($card_map[$num]) { $negative . $card_map[$num] ; #

return value } else { $negative . $num ; # return value } }

Tại đây chúng ta cho mảng @card_map một tên cho

số không.

Hai câu lệnh if đầu tiên đổi dấu của $num, và đặt

$negative thành từ phủ định, nếu số tìm được là bé hơn

không. Sau câu lệnh if này, giá trị của $num, bao giờ

cũng khác không, nhưng chúng ta sẽ có một xâu tiền tố

thích hợp trong $negative.

Câu lệnh if thứ hai xác định liệu $num (bây giờ

Page 326: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

314

dương) có bên trong mảng hay không. Nếu có, mảng kết

quả được gắn thêm phần tiền tố với $negative, và được

cho lại. Nếu không, giá trị bên trong $negative được gắn

với số gốc.

Câu lệnh if thứ ba có thể được thay thế bằng biểu

thức:

$negative . ($card_map[$num] || $num) ;

Chương 9, Các cấu trúc điều khiển khác

1. Sau đây là một cách để thực hiện nó

sub card { } # from previous exercise

while ( ) { ## NEW ## print “Enter first number: “ ; chop ($first = <STDIN>) ; last if $first eq “end” ; ## NEW ## print “Enter second number: “ ; chop ($second = <STDIN>) ; last if $second eq “end” ; ## NEW ## $message = &card($first) . “ plus “ . &card($second) . “ equals “ . &card($first+$second) . “.\n” ; $message =~ s/^./\u$&/ ; print $message ; } ## NEW $$

Chú ý tới việc bổ sung thêm chu trình while và hai

toán tử last. Vậy đấy!

Page 327: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

315

Chương 10, Tước hiệu tệp và kiểm thử tệp

1. Sau đây là một cách để thực hiện nó

print “What file? “ ; chop ($filename = <STDIN>) ; open (THAFILE, “$filename”) || die “cannot open

$filename” ; while (<THAFILE>) { print “$filename: $_” ; # presume $_ ends in \n }

Hai dòng đầu tiên nhắc tên tệp, rồi tên đó được mở

với tước hiệu THAFILE. Nội dung của tệp này được đọc

bằng việc dùng tước hiệu này, và được in ra STDOUT.

2. Sau đây là một cách để thực hiện nó

print “Input file name: “ ; chop ($inputfilename = <STDIN>) ;

print “Output file name: “ ; chop ($outputfilename = <STDIN>) ; print “Search string: “ ; chop ($search = <STDIN>) ; print “Replacement string: “; chop ($replace = <STDIN>) ; open (IN, $infilename) || die “cannot open $infilename

for reading”; ## kiểm thử tuỳ chọn cho khỏi ghi đè... die “will not overwrite $outputfilename” if -e

$outpufilename ; open (OUT, $outfilename) || die “cannot create

$outfilename”; while (<IN>) { # read a line from file $a into $_ s/$search/$replace/g ; # change the lines print OUT $_; # print that line to file $b }

Page 328: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

316

close (IN); close (OUT);

Chương trình này dựa trên chương trình sao tệp đã

trình bầy trước đây trong chương này. Các tính năng mới

đưa vào ở đây để nhắc cho các xâu, và chỉ lệnh thay thế

ở giữa chu trình while, cũng như phép kiểm tra cho việc

ghi đè tệp.

3. Sau đây là một cách để thực hiện nó

while (<>) { chop ; # eliminate the newline print “$_ is readable\n” if -r ; print “$_ is writable\n” if -w _ ; print “$_ is executable\n” if -x _; print “$_ does not exist\n” unless -e ;

}

Chu trình while này đọc một tước hiệu tệp mỗi lần.

Sau khi bỏ đi dấu dòng mới, loạt các câu lệnh kiểm tra

tệp theo các phép khác nhau. Lưu ý rằng phép kiểm tra

thứ hai và tiếp sau đó dùng tước hiệu _ để khỏi phải hỏi

lặp đi lặp lại hệ điều hành về cùng một tệp.

4. Sau đây là một cách để thực hiện nó

while (<>) { chop ; $age = -M ; if ($oldest_age < $age) { $oldest_name = $_ ; $oldest_age = $age ; }

}

Page 329: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

317

print “The oldest file Ý $oldest_name “, “and is $oldest_age days old.\n” ;

Trước hết, chúng ta lặp trên từng tên tệp được đọc

vào. Dấu dòng mới bị bỏ đi, và thế rồi tuổi tính theo

ngày được tính bằng toán tử -M. Nếu tuổi cho tệp này

vượt quá tệp cũ nhất mà đã thấy cho tới giờ, chúng ta

nhớ tên tệp này và tuổi tương ứng của nó. Khởi đầu,

$oldest_age sẽ là 0, cho nên chúng ta đếm ở đó ít nhất

một tệp khác 0 ngày cũ.

Câu lệnh print cuối cùng sinh ra báo cáo khi chúng ta

hoàn thành.

Chương 11, Dạng thức

1. Sau đây là một cách để thực hiện nó

open (PW, “/etc/passwd”) || die “How did you get logged in?”

while (<PW>) { ($user, $passwd, $uid, $gid, $gcos) = split(/:/) ;

($real) = split(/,/, $gcos) ; write;

} format STDOUT = @<<<<<<< @>>>>>>> @<<<<<<<<<<<<<<<<<<<<<<< $user, $uid, $real .

Dòng đầu tiên mở tệp mật hiệu. Chu trình while xử

lí tệp mật hiệu theo từng dòng. Mỗi dòng được xé ra (với

định biên hai chấm). Nạp vào biến vô hướng. Tên thực

của người dùng được lấy ra trường GCOS. Câu lệnh cuối

cùng của chu trình while gọi tới việc ghi ra thiết bị hiển

thị tất cả các dữ liệu này.

Page 330: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

318

Dạng thức cho tước hiệu tệp STDOUT xác định ra

một dòng đơn giản với ba trường. Các giá trị bắt nguồn

từ ba biến vô hướng được cho giá trị trong chu trình

while.

2. Sau đây là một cách để thực hiện nó

# append to program from the first problem... format STDOUT_TOP = Username User ID Real Name ======== ====== ========= @<<<<<< @>>>>> @<<<<<<<< .

Tất cả mọi việc để làm tiêu đề cho chương trình

trước là thêm vào dạng thức đầu trang. Tại đây chúng ta

đặt tiêu đề cột vào các cột.

Để cho các cột thẳng hàng, tôi đã sao văn bản của

dạng thức STDOUT và dùng mốt ghi đè trong trình soạn

thảo văn bản của tôi để thay thế các trường @<<< bằng

===. đó là điều hay về tương ứng một-một giữa các kí tự

trong dạng thức và kết quả hiển thị.

3. Sau đây là một cách để thực hiện nó

# append to program from the first problem... format STDOUT_TOP = Page @<<< $% Username User ID Real Name ======== ====== ========= .

Page 331: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

319

Được rồi, lần nữa ở đây lại được chất liệu ở đầu

trang. Tôi đã thêm vào dạng thức đầu trang. Dạng thức

này cũng chứa một tham chiếu tới $%, sẽ cho việc đánh

số trang.

Chương 12, Truy nhập danh mục

1. Sau đây là một cách để thực hiện nó

print “Where to? “ ; chop ($newdir = <STDIN>) ; chdir ($newdir) || die “Cannot chdir to $newdir” ; foreach (<*>) { print “$_\n”; }

Hai dòng đầu nhắc và đọc tên của danh mục.

Dòng thứ ba cố gắng thay đổi danh mục sang tên đã

cho, bỏ ra nếu điều này là không thể được.

Chu trình foreach đi qua danh sách. Nhưng danh

sách là gì? Đó là việc dò qua trong hoàn cảnh mảng, mở

rộng tới một danh sách tất cả các tên tệp mà sánh đúng

với mẫu này (ở đây là *).

2. Sau đây là một cách để thực hiện nó

print “Where to? “ ; chop ($newdir = <STDIN>) ; chdir ($newdir) || die “Cannot chdir to $newdir” ; opendir (DOT, “.”) || die “Cannot opendir . (serious

dainbramage)” ; foreach (sort readdir(DOT)) { print “$_\n”; }

Page 332: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

320

close (DOT) ;

Giống như chương trình trước, chúng ta nhắc và đọc

danh mục mới. Một khi chúng ta đã đổi danh mục chdir

ở đó, chúng ta mở danh mục bằng việc tạo ra một tước

hiệu danh mục có tên DOT. Trong chu trình foreach,

danh sách được cho lại bởi readdir (trong hoàn cảnh

mảng) được sắp xƠpm và rồi được duyệt qua, bằng việc

gán từng phần tử lần lượt cho $_.

Và đây là cách thực hiện nó với glob:

print “Where to? “ ; chop ($newdir = <STDIN>) ; chdir ($newdir) || die “Cannot chdir to $newdir” ; foreach (sort <* .*>) { print “$_\n”; }

Vâng, về cơ bản nó là chương trình khác với bài tập

trước đây, nhưng tôi đã thêm toán tử sort vào phía trước

của glob, và cũng thêm .* vào glob để lấy ra các tệp bắt

đầu với dấu chấm. Vhúng cần sort bởi vì một tệp có

tên !fred đi trước tệp chấm, nhưng barney đi sau chúng,

và không có glob dễ dàng có thể làm cho tất cả chúng

theo trình tự đúng.

Chương 13, Thao tác tệp và danh mục

1. Sau đây là một cách để thực hiện nó

unlink(@ARGV) ;

Đấy, có vậy thôi. Mảng @ARGV là một danh sách

các tên cần phải loại bỏ. Toán tử unlink nhận một danh

sách các tên, cho nên chúng chỉ lấy hai tên là đã xong.

Page 333: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

321

Tất nhiên, điều này không giải quyết việc báo cáo

lỗi, hay các tuỳ chọn -f hay -i, hay bất kì cái gì giống thế,

nhưng đó chỉ là cái lợi bất ngờ. Nếu bạn làm thế, tốt!

2. Sau đây là một cách để thực hiện nó

($old, $new) = @ARGV; # name them if (-d $new) { # new name is a directory, need to patch it

up ($basename = $old) =~ s#.*/## ; # get basename of

$old $new .= “/$basename” ; and append it to new name } rename ($old, $new) ;

Toàn bộ công việc trong chương trình này là ở dòng

cuối, nhưng phần còn lại của chương trình này là cần

thiết cho trường hợp khi tên chúng ta đang nhằm đổi là

một danh mục.

Trước hết, chúng ta đặt tên dễ hiểu cho hai phần tử

của @ARGV. Thế rồi, nếu tên $new là một danh mục,

chúng ta cần vá thêm cho nó bằng việc thêm tên mới vào

tên cơ sở của tên $old ở cuối. Điều này có nghĩa là đổi

tên /usr/src/fred thành /etc sẽ gây ra việc đổi tên

/usr/src/fred thành /etc/fred

Cuối cùng, một khi tên cơ sở được vá thêm, chúng

về gốc, với lời gọi rename.

3. Sau đây là một cách để thực hiện nó

($old, $new) = @ARGV; # name them if (-d $new) { # new name is a directory, need to patch it

up

Page 334: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

322

($basename = $old) =~ s#.*/## ; # get basename of $old

$new .= “/$basename” ; and append it to new name } link ($old, $new) ;

Chương trình này giống như chương trình trước

ngoại trừ dòng cuối cùng, bởi vì chúng móc nối chứ

không đổi tên.

4. Sau đây là một cách để thực hiện nó

if ($ARGV[0] eq “-s”) { # want a symlink $symlink++ ; # remember that shift (@ARGV) ; # and toss the -s flag }

($old, $new) = @ARGV; # name them if (-d $new) { # new name is a directory, need to patch it

up ($basename = $old) =~ s#.*/## ; # get basename of

$old $new .= “/$basename” ; and append it to new name } if ($symlink) { # want a symlink $symlink($old, $new) ; } else { # want a hard link link ($old, $new) ; }

Phần giữa của chương trình này cũng giống như hai

bài tập trước. Cái mới là ở vài dòng đầu và vài dòng

cuối.

Vài dòng đầu nhìn vào đối thứ nhất của chương

trình. Nếu đối này là -s, biến vô hướng $symlink được

tăng lên, làm sinh ra giá trị 1 cho biến này. Mảng

Page 335: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

323

@ARGV được dịch đi, bỏ cờ -s. Nếu như cờ -s không có

đó, chẳng phải làm gì, và $symlink sẽ vẫn còn là undef.

Việc dịch chuyển mảng @ARGV xuất hiện thường

xuyên đến mức mảng @ARGV là đối ngầm định cho

shift - tức là chúng ta có thể nói:

shift;

thay cho

shift (@ARGV);

Vài dòng cuối nhìn vào giá trị của $symlink. Nó hoặc

là 1 hoặc là undef, và dựa trên đó, sẽ symlink các tệp hay

link chúng.

5. Sau đây là một cách để thực hiện nó

foreach $f (<*>) { print “$f -> $where\n” if $where = readlink($f) ; }

Biến vô hướng $f được bật cho từng tên tệp trong

danh mục hiện tại. Với mỗi tên, $where lấy tập các tên đó

từ readlink(). Nếu tên này không phải là một symlink,

toán tử readlink cho lại undef, cho lại một giá trị sai cho

phép kiểm tra if , và print bị nhảy qua. Nhưng khi toán tử

readlink cho lại một giá trị, print hiển thị các giá trị nguồn

và nhận symlink.

Chương 14, Quản lí tiến trình

1. Sau đây là một cách để thực hiện nó

if (`date` =~ /^S/) { print “Go play~\n” ;

Page 336: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

324

} else { print “Get to work!\n” ; }

Điều hay xảy ra là kí tự ra đầu tiên của chỉ lệnh date

chỉ là một S vào cuối tuần (Sat hay Sun), làm cho

chương trình thành tầm thường. Chúng ta gọi date, rồi

dùng biểu thức chính qui để xem liệu kí tự đầu tiên có là

S hay không. Dựa trên điều này, chúng ta in ra thông báo

này hay khác.

2. Sau đây là một cách để thực hiện nó

open (PW, “/etc/passwd”) ; while (<PW>) { chop; ($user, $pw, $uid, $gid, $gcos) = split(/:/) ; ($real) = split(/,/.$gcos) ; $real {$user} = $real ; } close (PW) ; open(WHO, “who|”) || die “cannot open who pipe”; while (<WHO>) { ($login, $rest) = /^(\S+)\s+(.*)/; $login = $real{$login} if $real{$login} ;

printf “%-30s %s\n”, $login, $rest ; }

Chu trình thứ nhất tạo ra một mảng kết hợp %real

lấy các tên đăng nhập làm khoá và các tên thực tương

ứng làm giá trị. Mảng này được dùng trong chu trình tiếp

sau để thay đổi tên đăng nhập thành tên thực.

Chu trình thứ hai duyệt qua cái ra kết quả từ việc mở

chỉ lệnh who xem như tước hiệu tệp. Mỗi dòng cái ra của

Page 337: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

325

who lại được bẻ ra bằng việc dùng việc sánh biểu thức

chính qui trong hoàn cảnh mảng. Từ đầu tiên của dòng

này (tên đăng nhập) được thay thế bằng tên thật trong

mảng này, nhưng chỉ nếu nó tồn tại. Khi tất cả những

điều đó đã được thực hiện, printf đưa kết quả ra

STDOUT.

Bạn có thể thay thế việc mở tước hiệu tệp và việc

bắt đầu chu trình chỉ bằng:

foreach $_ (`who`) {

để hoàn thành cùng kết quả. Sự khác biệt duy nhất là

ở chỗ bản với tước hiệu tệp có thể bắt đầu làm việc ngay

khi who bắt đầu chẻ các kí tự, trong khi bản với who

trong dấu nháy đơn ngược phải đợi để who kết thúc.

3. Sau đây là một cách để thực hiện nó

open (PW, “/etc/passwd”) ; while (<PW>) { chop; ($user, $pw, $uid, $gid, $gcos) = split(/:/) ; ($real) = split(/,/.$gcos) ; $real {$user} = $real ; } close (PW) ; open(LPR, “|lpt”) || die “cannot open LPR pipe”; open(WHO, “who|”) || die “cannot open who pipe”; while (<WHO>) { # or replace previous two lines with: foreach $_

(`who`) { ($login, $rest) = /^(\S+)\s+(.*)/; $login = $real{$login} if $real{$login} ;

printf LPR “%-30s %s\n”, $login, $rest ; }

Page 338: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

326

Sự khác biệt giữa chương trình này và chương trình

trong bài tập trước là ở chỗ chúng ta đã bổ sung thêm

một tước hiệu tệp LPR được mở đối với tiến trình lpr, và

sửa đổi câu lệnh printf để gửi dữ liệu ra đó thay vì ra

STDOUT.

4. Sau đây là một cách để thực hiện nó

sub mkdir { !system “/bin/mkdir”, @_ ;

}

Tại đây, chỉ lệnh mkdir được cho các đối trực tiếp từ

các đối của chương trình con. Tuy nhiên, giá trị cho lại

phải được phủ định về mặt logic bởi vì một trạng thái ra

khác không từ system phải dịch thành giá trị sai cho nơi

gọi Perl.

5. Sau đây là một cách để thực hiện nó

sub mkdir { local($dir, $mode) = @_; (!system “/bin/mkdir”, $dir) && chmod ($mode, $dir) ;

}

Đầu tiên, các đối cho trình này được đặt tên là $dir

và $mode. Tiếp đó, chúng ta gọi mkdir trên danh mục

được $dir chỉ tên. Nếu điều đó thành công, toán tử chmod

cho lại mốt đúng cho danh mục này.

Chương 15, Biến đổi dữ liệu khác

1. Sau đây là một cách để thực hiện nó

Page 339: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

327

while (<>) { chop; $slash = rindex($_, “/”) ; $head = substr($_,0,$slash) ; $tail = substr($_, $slash+1) ; print “head = ‘$head’, tail = ‘$tail’\n” ; }

Mỗi dòng được đọc bởi toán tử hình thoi đã được

chặt bỏ (dấu dòng mới phiền phức) trước hết. Tiếp đó

chúng ta tìm dấu sổ chéo bên phải nhất trong dòng này,

bằng việc dùng rindex(). Hai dòng tiếp bẻ xâu này ra bằng

việc dùng substr(). Lưu ý rằng các biểu thức này “làm

đúng việc” cho dù khi kết quả của rindex là -1 (không có

dấu sổ chéo trong xâu). Dòng cuối cùng bên trong chu

trình in ra kết quả.

2. Sau đây là một cách để thực hiện nó

chop(@nums = <STDIN>) ; # note special use of chop @nums = sort { $a <=> $b } @nums; foreach (@nums) { printf “%30g\n”, $_ ; }

Dòng thứ nhất nắm lấy tất cả các số trong mảng

@nums. Dòng thứ hai sắp xếp mảng theo số, bằng cách

dùng một định nghĩa trong dòng cho thứ tự sắp xếp. Chu

trình foreach in ra kết quả.

3. Sau đây là một cách để thực hiện nó

open (PW, “/etc/passwd”) || die “How did you get logged in?”

while (<PM>) {

Page 340: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

328

chop ; ($user, $pm, $uid, $gid, $gcos) = split(/:/) ; ($real) = split(/,/,$gcos) ; $real {$user} = $real ; ($last = $real) =~ s/^(.*[^a-z]) ? ([^a-z]+.*).*/$2/i ; $last =~ tr/A-Z/a-z/; $last {$user} = $last;

} close (PW) ; for (sort by_last keys %last) { printf “%30s %8s\n”, $real{$_}, $_ ; } sub by_last { $last{$a} cmp $;ast{$b}) || ($a cmp $b)}

Chu trình đầu tiên tạo ra mảng %last, bao gồm tên

đăng nhập làm khoá, và tên cuối của người dùng là giá

trị tương ứng, và mảng %real , chứa các tên thực đầy đủ.

Chu trình thứ hai in ra %real được sắp thứ tự theo

các giá trị của %last , dùng định nghĩa sắp xếp được trình

bầy trong chương trình con by_last.

4. Sau đây là một cách để thực hiện nó

while (<>) { substr($_,0,1) =~ tr/a-z/A-Z/; substr($_,1) =~ tr/A-Z/a-z/; print ; }

Với mỗi dòng được toán tử hình thoi đọc vào, dùng

hai phép toán tr, mỗi phép toán trên một phần khác nhau

của xâu này. Toán tử tr thứ nhất biến kí tự đầu tiên của

dòng thành chữ hoa, còn toán tử tr thứ hai, biến phần còn

lại thành chữ thường. Kết quả được in ra.

Page 341: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

329

Một cách khác để thực hiện việc này, bằng cách

dùng toán tử xâu nháy kép, là:

while (<>) { print “\u\L$_”; }

cho mình năm điểm phụ nếu bạn đã nghĩ tới điều

đó.

Chương 16, Truy nhập cơ sở dữ liệu hệ thống

1. Sau đây là một cách để thực hiện nó

$: = “ “ ; while (@pw = getpwent) { ($user, $gid, $gcos) = @pw[0,3,6];

($real) = split(/,/,$gcos) ; $real { $user} = $real ; $member {$gid} .= “ $user” ; ($last = $real) =~ s/^(.*[^a-z]) ? ([a-z]+.*).*/$2/i; $last =~ tr/A-Z/a-z/; $last {$user} = $last;

} while (@gr = getgrent) { ($gname, $Gid, $members) = @gr[0,2,3];

$members {$gid} .= “ $members”; $gname {$grid} = $gname ;

} for $gid (sort by_gname keys %gname) { %all = () ; for (split (/\s+/, $members {$gid} )) {

$all {$_} ++ if length $_ ; } @members = () ; foreach (sort by_last key %all) {

Page 342: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

330

push(@members, “$real {$_} ($_)”) ; } $members = join(“, “, @members); write;

} sub by_gname { $gname {$a} cmp $gname {$b};} sub by_last { ($last {a} cmp $last {$b}) || ($a cmp $b) ; } format STDOUT = @<<<<<<<< @<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<< $gname {$gid}, “ ($gid)”, $memberlist ~~ ^<<<<<<<<<<<<<<<<<<<<< $memberlist .

Chương 17, Thao tác cơ sở dữ liệu người dùng

1. Sau đây là một cách để thực hiện nó

dbmopen(ALIAS, “/etc/aliases”, undef) || die “No aliases!”;

while ($key, $value) = each (%ALIAS)) { chop ($key, $value) ; print “$key $value\n” ; }

Dòng thứ nhất mở các biệt hiệu DBM. (Hệ thống

của bạn có thể giữ các biệt hiệu DBM trong

/usr/lib/aliases - thử điều đó nếu điều này không làm

việc.) Chu trình while duyệt qua mảng DBM. Dòng thứ

nhất bên trong cho trình sẽ cắt bỏ đi kí tự NUL tại cuối

khoá và giá trị. Dòng cuối cùng của chu trình này in ra

kết quả.

Page 343: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

331

2. Sau đây là một cách để thực hiện nó

# program 1: dbmopen(%WORDS, “words”, 0644) ; while (<>) { foreach $word (split(/\W+/)) {

$WORDS {$word}++; }

} dbmclose (%WORDS);

Chương trình thứ nhất (người viết) mở một DBM

trong danh mục hiện tại được gọi là words, tạo ra các tệp

có tên words.dir và words.pag. Chu trình while lấy từng

dòng bằng việc dùng toán tử hình thoi. Dòng này được

chẻ ra từng phần bằng việc dùng toán tử split, với định

biên /\W+/, có nghĩa là kí tự không là từ. Mỗi từ tiếp đó

được đếm trong mảng DBM, bằng việc dùng câu lệnh

foreach để đi qua các từ.

# program 2: dbmopen(%WORDS, “words”, undef) ; foreach $word (sort { $WORDS {$b} <=> $WORDS {$a} }

keys %WORDS) {

print “$word $WORDS {$word}\n” ; } dbmclose (%WORDS);

Chương trình thứ hai mở một DBM trong danh mục

hiện tại có tên là words. Dòng foreach trông phức tạp, đã

làm hầu hết các công việc bẩn thỉu. Giá trị của $word

mỗi lần qua chu trình này sẽ là phần tử tiếp của danh

sách. Danh sách này được sắp thứ tự theo khoá trong

mảng %WORDS, sắp theo giá trị của chúng (số đếm)

theo thứ tự giảm dần. Với mỗi từ trong danh sách, chúng

ta in ra từ đó và số lần từ đã xuất hiện.

Page 344: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

332

Chương 18, Chuyển các ngôn ngữ khác sang Perl

1. Sau đây là một cách để thực hiện nó

for ( ; ; ) { ($user, $home) = (getpwent) [0,7]; last unless $user; next unless open (N, “$home/.newsrc”) ; while (<N>) { if (/^comp\.lang\.perl:/) { print “$user Ý a good person, “, “and reads comp.lang.perl!\n”) ; last; } }

}

Chu trình bên ngoài nhất là chu trình for mà cứ chạy

mãi - tuy nhiên chu trình này ra bằng toán tử last bên

trong. Mỗi lần qua chu trình, một giá trị mới cho $user

(tên người dùng) và $home (danh mục nhà của họ) được

lấy ra bằng việc dùng toán tử getpwent.

Nếu giá trị của $user là rỗng, chu trình for thoát ra.

Hai dòng tiếp tìm tệp .newsrc mới đây trong danh mục

này của người dùng. Nếu không thể mở được tệp này,

hay thời gian sửa đổi cho tệp này là quá xa, việc lặp tiếp

cho chu trình for sẽ được bật lẫy.

Chu trình while đọc một dòng mỗi lúc từ

tệp .newsrc. Nếu dòng này bắt đầu với comp.lang.perl:,

câu lệnh print nói thế, và cho trình while ra sớm.

Page 345: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

333

Phụ lục B

Cơ sở về nối mạng

Mọi người bao giờ cũng tới gặp tôi trên phố, hàng

chục lần một ngày, và hỏi, “Này, Randal, làm sao tôi làm

cho Perl giải quyết khe cắm vào/ra?”

Được, tôi muốn dạy về TCP/IP, nhưng không may

là lề của trang này quá hẹp. Cho nên, bạn phải giải quyết

một thí dụ làm việc thật, và có lẽ phải đợi

O’Reilly&Associates đưa ra một cuốn sách Perl nâng

cao.

Mô hình khe cắm

Vấn đề đại loại là thế này:

1. Tiến trình phục vụ tạo ra một khe cắm tổng quát

bằng socket().

2. Bộ phục vụ kết ghép khe cắm này với một địa chỉ đã

thoả thuận qua bind().

Page 346: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

334

3. Bộ phục vụ lưu ý hệ thống rằng nó đã nối qua listen().

4. Bộ phục vụ ngồi sau và đợi lần nối ghép đầu tiên qua

accept().

5. Tiến trình khách tạo ra một khe cắm tổng quát bằng

socket().

6. Khách kết ghép khe cắm này với bất kì địa chỉ nào

do hệ thống chọn qua bind(). (Việc kết ghép có thể

xảy ra trong bước tiếp nếu khách không cầu kì về địa

chỉ.)

7. Một khi đã được kết ghép, khách nối khe cắm của

mình với khe cắm của bộ phục vụ qua connect(), bằng

việc dùng địa chỉ đã thoả thuận. Điều này thiết lập

lên một ghép nối.

8. Bộ phục vụ để ý đến ghép nối mới. Các bộ phục vụ

điển hình phân nhánh cho một bộ phục vụ con để

giải quyết việc ghép nối đặc biệt, với bộ phục vụ bố

mẹ giải quyết cho ghép nối tiếp. (Bộ phục vụ mẫu

của chúng không phân nhánh về sau, bởi vì nó chỉ

làm một việc ngắn và tháo bỏ ghép nối này. Điều này

sẽ là vấn đề nếu 20 yêu cầu tới một lúc.)

9. Bộ phục vụ con đọc dữ liệu từ khe cắm đã được

khách gửi. Bộ phục vụ con cũng ghi dữ liệu lên khe

cắm, mà do khách có sẵn. Trong Perl, vào/ra được

thực hiện dường như nó là một tước hiệu tệp bình

thường.

10. Khi bộ phục vụ con và khách đã kết thúc nói chuyện,

chúng đóng tước hiệu tệp, do vậy kết thúc việc ghép

nối.

Page 347: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

335

Một khách mẫu

Và là một khách đơn giản. Khách làm việc thực tế

này nói tới một địa chỉ đã xác định (trong trường hợp

này, “daytime” chuẩn) trên một máy chủ đặc biệt (máy

chủ cục bộ), và in ra bất kì cái ra nào mà cổng sinh ra.

require ‘sys/socket.ph’; $sockaddr = ‘ S n a4 x8’; chop ($hostname = `hostname`) ; ($name, $aliases, $proto) = getprotobyname(‘tcp’); ($name, $aliases, $port) = getservbyname(‘daytime’,

‘tcp’); ($name, $aliases, $type, $len, $thisaddr) =

gethostbyname (‘$hostname’); $thisport = pack($socaddr, &AF_INET, 0, $thisaddr); $thatport = pack($socaddr, &AF_INET, $port, $thisaddr); socket(S, &PF_INET, &SOCK_STREAM, $proto) || die “cannot create socket\n” ; bind (S, $thisport) || die “cannot bind socket\n”; # optional connect (S, $thatport) || die “cannot connect socket\n” ; while (<S>) { print; } exit 0;

Bộ phục vụ mẫu

Và là một bộ phục vụ đơn giản. Bộ phục vụ này đặt

khe cắm tại địa chỉ 4242 trên máy hiện tại. Bất kì ai nối

với cổng này đã lấy được bánh may mắn đa tuyến. Mỗi

bánh may mắn đã được dùng một lần (ngẫu nhiên) cho

tới khi tất cả các bánh đã được ăn hết. Vậy cơ sở bánh

được khởi động lại từ đầu.

Page 348: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

336

Bạn có thể dùng chương trình này với khách mẫu

bằng việc thay thế đường tính $port bởi câu lệnh đơn

$port = 43242;. Hay bạn có thể nói telnet localhost 4242 để

quan sát cái ra - sự chọn lựa của bạn.

require ‘sys/socket.ph’; $sockaddr = ‘ S n a4 x8’; chop ($hostname = `hostname`) ; ($name, $aliases, $proto) = getprotobyname(‘tcp’); $port = 4242; # some big number $thisport = pack($socaddr, &AF_INET, $port, “\0\0\0\0”); #wildcard addr socket(S, &PF_INET, &SOCK_STREAM, $proto) || die “cannot create socket\n” ; bind (S, $thisport) || die “cannot bind socket\n”; listen(S,5) || die “cannot listen socket\n”; for (; ;) { accept(NS,S) || die “cannot accept socket\n”; print NS &fortune; close NC; } sub fortune { @fortunes = split(/\n%%\n/, <<’END’) unless

@fortunes; A fool and hs money are soon parted. %% A penny saved is a penny earned. %% Ask not what your country can do for you; ask what you can do for your country. %% END splice(@fortunes, int(rand(@fortunes)), 1). ”\n”’ }

Page 349: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

337

Phụ lục C

Các chủ đề còn chưa

nói tới

Vâng, điều này đáng ngạc nhiên. Cuốn sách này quả

dài, thế mà vẫn còn cái gì đó nó vẫn chưa bao quát hết.

Các chú thích cuối trang chứa thêm thông tin phụ.

Mục đích của mục này không phải là để dạy cho bạn

về những điều đã được liệt kê ra ở đây, mà đơn giản đưa

ra một danh sách. Bạn sẽ cần tới Sách con lạc đà hay tài

liệu dùng Perl (trên nhóm hỗ trợ Usenet) để có thêm

thông tin.

Trình gỡ lỗi

Perl có trình gỡ lỗi mức nguồn tuyệt vời.

Page 350: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

338

Dòng lệnh

Bộ thông dịch Perl có quá thừa thãi các khoá dòng

lệnh.

Các toán tử khác

Toán tử phẩy là một. Và có cách diễn đạt do { block; }

khác có trong tay khi bạn cần một khối câu lệnh ở nơi

đòi hỏi biểu thức.

Và có một số biến thể về các phép toán, giống như

việc dùng bộ sửa đổi g cho việc đối sánh.

Nhiều, nhiều hàm nữa

Vâng, Perl có thật nhiều hàm, tôi không định liệt kê

chúng ra đây, bởi vì cách nhanh nhất để tìm ra chúng là

đọc qua mục các hàm trong cuốn Lập trình Perl hay tài

liệu về perl và nhìn vào bất kì cái gì bạn nhận ra không

đáng quan tâm.

Nhiều, nhiều biến định nghĩa sẵn

Bạn đã thấy vài biến đã xác định trước, như $_.

Được, còn nhiều nữa cơ.

Xâu ở đây

Bên cạnh các xâu nháy đơn và nháy kép, bạn cũng

có thể có các xâu ở đây, giống như tài liệu ở đây trong

lớp vỏ:

Page 351: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

339

$a = <<”HEAD” . “\n” . $body; To: [email protected] From: $username Subject: What, do you think? Date: $now HEAD

Chúng thực sự là không cần thiết, bởi vì bạn có thể

chỉ viết ra một xâu nháy kép dài thay thế, nhưng vài

người thấy chúng rõ ràng. Và cách đó bạn có thể nói “Có

nhiều cách trích dẫn nó!”

Trở về (từ trình con)

Có câu lệnh return để ra ngay lập tức khỏi chương

trình con, kéo theo một giá trị cùng nó. Nhưng trong Perl

4.0 nó có hơi không hiệu quả, cho nên tôi bỏ lại nó ra

khỏi mô tả này. Tuy thế bạn cứ thoảo mái mà dùng nó.

Toán tử eval (và s///e)

Vâng, bạn có thể xây dựng một mẩu chương trình

vào lúc chạy rồi tính eval nó, như bạn có thể làm với lớp

vỏ. Nó thực tế có ích, bởi vì bạn có thể thu được những

tối ưu về thời gian dịch (như biểu thức chính qui đã được

dịch) vào lúc chạy. Bạn cũng có thể dùng nó để bẫy các

lỗi định mệnh khác trong một đoạn chương trình: lỗi

định mệnh bên trong eval đơn giản ra khỏi eval và cho

bạn trạng thái lỗi.

Chẳng hạn, sau đây là một chương trình đọc một

dòng chương trình Perl từ người dùng và rồi thực hiện

nó dường như nó là một phần của chương trình Perl:

Page 352: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

340

print “code line: “ ; chop ($code = <STDIN>) ; eval $code; die “eval: $@” if $@ ;

Bạn có thể đặt chương trình Perl bên trong xâu thay

thế của toán tử thế với cờ e. Điều này làm thủ công nếu

bạn muốn xây dựng cái gì đó phức tạp cho xâu thay thế,

như gọi trình con cho lại kết quả của việc tra cơ sở dữ

liệu. Sau đây là một chu trình làm tăng giá trị của cột thứ

nhất của một loạt dòng:

while (<>) { s/^(S+)/$1+1/e; # $1+1 is Perl code, not a string print ; }

Thao tác bảng kí hiệu bằng *FRED

Bạn có thể làm b thành một biệt hiệu cho a qua *b =

*a. Điều này có nghĩa là $a và $b tham chiếu tới cùng

một biến, như @a và @b, và thậm chí các tước hiệu tệp

và dạng thức a và b. Bạn cũng có thể định vị *b bên trong

một khối với local(*b), và điều đó để cho bạn có tước hiệu

tệp cục bộ và dạng thức và các thứ khác. Một chất liệu

khá đồng bóng nhưng có ích khi bạn cần nó. Perl 5.0

thậm chí còn có cách đặt biệt hiệu phức tạp hơn (giống

nhiều hơn với con trỏ của C) nhưng khi viết điều này, tôi

chưa đủ tư liệu về nó.

Toán tử goto

Vâng, có, Perl có toán tử goto. Nhưng nó không

hiệu quả khủng khiếp, và nó tồn tại chỉ để hỗ trợ cho bộ

dịch sed sang Perl. Cho nên đừng dùng nó. Bạn thực sự

Page 353: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

341

không cần nó. Và chương trình Perl có thể trở thành đủ

khó hiểu cho dù không có một đống các goto.

Toán tử require

Toán tử require là dạng của include của Perl. Các

phần của chương trình Perl có thể bị mắc kẹt vào trong

một tệp tách biệt (hay tập các tệp) và rồi được đưa vào

trong bất kì chương trình Perl nào cần chúng. Chẳng

hạn:

require ‘fred.pl’ ;

đưa vào trong văn bản của tệp fred.pl dường như nó

là một phần của tệp này, cho phép nhiều chương trình

dùng chung mã Perl.

Thư viện

Nói về chùm các chương trình Perl được bao hàm,

nhiều người thạo (và tôi) đã đóng góp nhiều chương

trình để làm những việc có ích. Để tìm ra thư viện Perl ở

đâu, yêu cầu Perl in ra phần tử đầu tiên của mảng @INC.

(Nếu không có gì trong danh mục đó, Perl của bạn đã

được cài đặt không đúng.) Gần như tất cả các trình này

đã có lời chú thích trong chúng để mô tả cách sử dụng.

Tin vui về Perl 5.0

Và thậm chí còn có cả chùm những chất liệu mà

không ai (ngoại trừ Larry Wall) biết tới, vì bản mới nhất

của Perl không được đưa ra vào lúc tôi gõ những điều

Page 354: Perl - science-technology.vnscience-technology.vn/wp-content/uploads/2013/07/Hoc-Perl.pdf · Học Perl Randal L. Schwartz Người dịch: Ngô Trung Việt Hà Nội 5/1999

342

này. Bởi thế, bạn phải kiểm tra trong tài liệu xem Larry

đã tiến hành những nỗ lực lớn để đảm bảo rằng sự khác

biệt giữa bản bạn đang chạy và sách in đã được làm tài

liệu tốt. Cám ơn, Larry.