print “Xin chào mọi người\n”; #c ũng cùng điều đó
17 Thao tác cơ sở dữ liệu người dùng
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 đều 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 mà 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
đĩạ 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 thì 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 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ủ cập nhất cũng dùng cơ sở dữ liệu DBM để giữ dấu vết cac bài báo hiện tại và mới xem gần đấỵ
Perl cung cấp việc thâm 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 để thâm 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 thì có hạn chế, và một mảng DBM có cùng những hạn chế đó. Bạn hãy 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 thì 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, bạn hãy 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ị thì các giá trịđó đều bị bỏđị) 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 giữ trên đĩa như một cặp tệp có tên dbmfilenam.dir và dbmfilenamẹpag. Bất kì tên mảng kết hợp hợp pháp nào cũng đều có thể dùng được, mặc dầu các tên mảng chỉ toàn chữ hoa thì đượ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 rạ 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ủ thì có phép đọc-ghị Nếu các tệp này đã tồn tại thì 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 mydatabasẹdir
và mydatabasẹpag trong danh mục hiện tạị Nếu các tệp này chưa tồn tại thì 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 thì hãy 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 thì lời gọi dbmopen() sẽ cho lại sai, thay vì cố gắng tạo ra các tệp nàỵ
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 thì sự kết hợp sẽ 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 saị
Dùng mảng DBM
Một khi cơ sở dữ liệu đã được mở thì các tham khảo tới mảng DBM sẽ được ánh xạ vào các tham khảo cơ sở dữ liệụ 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 đĩạ Chẳng hạn, một khi %FRED được mở trong thí dụ trước, thì ta có thể thêm vào, xoá bỏ
hay thâm 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 nhòm qua toàn bộ tệp đĩa hai lần: một lần để thâm nhập vào khoá, và lần thứ hai để tra cứu các giá trị từ khoá. Nếu chúng ta nhòm qua một mảng DBM thì 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 thâm nhập vào cơ sở dữ liệu DBM hệ thống, như các cơ sở dữ
liệu đã được tạo ra bởi sendmail hay tin Usenet, thì bạn phải biết rằng các chương trình nói chung đều gắn thêm cái đuôi kí tự NUL (“\0”) vào cuối của các xâu nàỵ 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ệụ 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ĩạ Chẳng hạn, để tra cứu merlyn trong cơ sở dữ liệu biệt hiệu, bạn hãy thử một điều gì đó kiểu:
dbmopen(%ALI, “/etc/aliases”, undef) || die “Không biệt hiệủ”; $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 rạ
Cơ sở dữ liệu thâm 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 bỉ 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ưu nhaụ Việc đánh số cho các bản ghi thì không quan trọng hay 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ổị Mỗi bản ghi vậy là có chiều dài 83 bytẹ Néu ta đọc tất cả dữ liệu trong cơ sở dữ liệu thì ta phải đọc từng chùm 83 byte cho tới khi đến cuốị Nếu ta muốn đi tới bản ghi thứ năm thì ta phải nhẩy qua 4 lầm 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à ghị 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 thâm nhập đọc/ghi
Lưu ý rằng tất cả những điều ta đã làm đều mới chỉ là bổ sung thêm dấu cộng vào hướng vào/rạ
Một khi ta đã thu được việc mở tệp thì ta 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ứ bạ 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 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 thì việc đưa vào hay đưa ra tiếp sẽ
bắt đầu từ đó. Với việc đưa ra, hãy 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àị Để 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, $miđle, $last, $age);
Bộ xác định pack()đó cho 49 kí tựđối với $first, một kí tự cho $miđle, 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ế, ta dùng toán tử read(), trông và cách làm việc thì 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 mà 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ự thì chỉ cần chặt nó ra thành các thành phần bằng toán tử unpac():
($first, $miđle, $last, $age) = unpack(“A40A40s”, $buf);
Lưư ý rằng các xâu định dạng cho pack và unpack là như nhaụ Phần lớn các chương trìnhcấ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
tạo ra) đều 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 soan 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 tahy đổ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 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 thì mốt soạn thảo này được thâm 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 đâỵ
Để đặt lẫy cho mốt soạn thảo tại chỗ, bạn hãy đặ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 đâỵ Khi toán tử hình thoi được sử dụng và $^I có một giá trị (thay vì undef), thì 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 thì lại chuyển sang một bản sao mới của tệp nàỵ 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ớị) 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 thảo tạo rạ 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 thì 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ậỵ
đổ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. Trong thực tế, 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 -ị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ừa về perl.
Bài tập
1. Hãy 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. Hãy 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 thì mở tệp DBN và cho hiển thị kết quả được sắp xếp theo số đếm giảm dần. Hãy 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.