Chuyển đến nội dung chính

8 lệnh Shell hữu ích cho Khoa học Dữ liệu

Nhà khoa học dữ liệu dùng những lệnh shell nào gần như mỗi ngày? Khám phá và học cách dùng trong hướng dẫn này!
· 12 phút đọc

Khám phá cùng AI

Có rất nhiều tình huống bạn cần phân tích, chỉnh sửa và xử lý nhanh các tệp lớn, cả về số lượng lẫn dung lượng. Tuy nhiên, các tệp này thường là tệp văn bản dạng CSV (Comma Separated Values). Mở chúng bằng các ứng dụng bảng tính tiêu chuẩn như Excel, LibreOffice hay OpenOffice sẽ làm quá tải bộ nhớ máy. Ngoài ra, xử lý một số lượng lớn tệp trong một mẻ thường thất bại sau vài giờ do một số bất thường không lường trước trong tệp.

Hệ quả là bạn tốn rất nhiều thời gian vì màn hình bị treo, khởi động lại và chờ đợi lâu.

Tuy vậy, phần lớn các tác vụ này có thể thực hiện chỉ với vài dòng lệnh. Hơn nữa, quen thuộc với một vài dòng lệnh shell đơn giản có thể giúp bạn tiết kiệm rất nhiều thời gian và giảm bực bội.

Bài viết này sẽ trình bày tổng quan về một số lệnh shell mà tôi sử dụng gần như mỗi ngày. Hy vọng bạn thấy hữu ích.

shell data science

Ghi chú nhanh

Lưu ý có nhiều loại shell (bash, zsh, ...), trong đó bash là phổ biến nhất vì là shell mặc định trên OS X và nhiều bản phân phối Linux. Zsh (và Oh My Zsh) là một lựa chọn thay thế mạnh mẽ và phổ biến. Các lệnh shell trong bài viết này đã được kiểm thử trên bash của OS X (macOS) và nhìn chung sẽ hoạt động với các shell và môi trường khác.

Tất cả các ví dụ sau đây dựa trên bộ dữ liệu adult từ kho UCI Machine Learning, còn được biết đến là bộ "Census Income" và có sẵn tại đây. Bộ dữ liệu này thường được dùng để dự đoán liệu thu nhập có vượt quá \$50K/năm dựa trên dữ liệu điều tra dân số hay không. Với 48842 hàng và 14 thuộc tính, đây không phải là một bộ dữ liệu lớn, nhưng đủ để minh họa các ví dụ. Mặc dù dữ liệu được lưu với phần mở rộng .data, thực chất đây là tệp CSV được định dạng tốt. Bạn cứ tải về để thực hành theo nhé!

Đếm với wc

Khi có một tệp văn bản mới, bạn muốn biết nó có bao nhiêu dòng. Việc này có thể thực hiện với lệnh đếm từ wc -l:

$ wc -l adult.data
  32562 adult.data

Kết quả cho biết tệp adult.data có 32562 dòng. Cờ -l yêu cầu wc đếm số dòng. Bạn cũng có thể dùng wc để đếm số từ với cờ -w.

$ wc -w adult.data
  488415 adult.data

Tệp adult.data có gần 500 nghìn từ.

Lệnh wc cũng có thể đếm số tệp trong một thư mục bằng cách dùng đầu ra của lệnh ls -l làm đầu vào cho wc. Dùng đầu ra của một lệnh làm đầu vào cho lệnh khác thông qua ký hiệu ống | là một mẫu hữu ích của shell gọi là pipelining. Bạn sẽ thấy nó trong nhiều ví dụ suốt bài viết.

Giả sử bạn có một thư mục với rất nhiều tệp. Lệnh sau sẽ cho biết chính xác nó chứa bao nhiêu tệp:

$ ls -l <folder> | wc -l
 508

Có rất nhiều ứng dụng khác cho wc nếu bạn bắt đầu thêm wildcard và thư mục con.

Quay lại tệp adult.data và xem các dòng đầu tiên với lệnh head. Mặc định, head in ra 10 dòng; bạn có thể giới hạn còn 2 dòng đầu với cờ -n.

$ head -n 2 adult.data
39, State-gov, 77516, Bachelors, 13, Never-married, Adm-clerical, Not-in-family, White, Male, 2174, 0, 40, United-States, <=50K
50, Self-emp-not-inc, 83311, Bachelors, 13, Married-civ-spouse, Exec-managerial, Husband, White, Male, 0, 0, 13, United-States, <=50K

Và bạn nhận thấy tệp không có dòng tiêu đề. Tên cột không có trong tệp. Bạn có thể thêm dòng tiêu đề bằng cách nối hai tệp với lệnh cat.

Nối tệp với cat

Lệnh cat in nội dung tệp ra thiết bị xuất chuẩn (tức terminal của bạn). Nó cũng có thể dùng để nối một loạt tệp. Lệnh cat file_1.csv file_2.csv > target_file.csv sẽ gộp nội dung của file_1.csv và file_2.csv vào target_file.csv, thêm file_2.csv vào cuối file_1.csv.

Tệp tiêu đề không có trong bộ dữ liệu gốc nên bạn cần tạo nó. Để làm vậy, bạn dùng echo để ghi danh sách tên cột ngăn cách bởi dấu phẩy vào tệp header.csv.

$ echo "age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class" > header.csv

Trong ví dụ này, bạn đã dùng ký tự chuyển hướng > của shell để đổ đầu ra của cat vào tệp adult.csv. Ký tự > sẽ tạo tệp hoặc ghi đè toàn bộ nội dung nếu tệp đã tồn tại. Dùng ký hiệu kép >> sẽ nối thêm nội dung mới vào tệp có sẵn mà không xóa nội dung cũ.

Giờ hãy thêm tệp header.csv vào đầu tệp adult.data. Đồng thời, đổi tên adult.data thành adult.csv vì suy cho cùng đây là tệp định dạng CSV.

$ cat header.csv adult.data > adult.csv

Kiểm tra rằng hàng đầu tiên của adult.csv chứa tên cột bằng:

$ head -n 1 adult.csv
age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class
39, State-gov, 77516, Bachelors, 13, Never-married, Adm-clerical, Not-in-family, White, Male, 2174, 0, 40, United-States, <=50K

Chỉnh sửa tệp với sed

Một tình huống khai thác dữ liệu thường gặp khác là khi tệp bị hỏng hoặc định dạng kém, chẳng hạn có ký tự không phải UTF-8 hoặc dấu phẩy đặt sai chỗ. Bạn có thể sửa tệp mà không cần mở nó bằng lệnh sed.

Mẫu chung của sed là

$ sed "s/<string to replace>/<string to replace it with>/g" <source_file> > <target_file>.

Bộ dữ liệu adult.csv dùng ký tự ? để biểu thị giá trị thiếu. Bạn có thể thay ký tự đó bằng một giá trị mặc định phù hợp hơn bằng sed. Chuỗi rỗng là lựa chọn tốt hơn để biểu thị giá trị thiếu vì sẽ được hiểu là giá trị NaN khi nạp dữ liệu vào Pandas DataFrame.

$ grep ", ?," adult.csv | wc -l
2399

Lệnh này cho biết có 2399 dòng có ít nhất một cột mang giá trị thiếu được biểu thị bằng ?. Lệnh sau sẽ thay tất cả các cột có ? bằng chuỗi rỗng. Mọi ô chỉ chứa ? theo sau bởi một khoảng trắng giờ sẽ thực sự rỗng.

$ sed "s/, ?,/,,/g" adult.csv >  adult.csv

Lưu ý bạn dùng dấu phân cách cột , trong chuỗi nguồn và chuỗi đích để tránh thay thế các dấu hỏi hợp lệ có thể xuất hiện ở nơi khác trong tập dữ liệu.

Trích xuất một phần từ tệp lớn

Hãy tưởng tượng bạn đang xử lý một tệp có hơn 30 triệu dòng. Khi soạn thảo script Python hoặc R, bạn sẽ muốn thử nghiệm quy trình trên một mẫu con của tệp gốc thay vì nạp toàn bộ dữ liệu vào bộ nhớ. Bạn có thể kết hợp head và tail để tạo một mẫu con từ bộ dữ liệu gốc.

  • head in phần đầu của tệp;
  • tail in phần cuối của tệp

Như đã thấy, các lệnh này nhận cờ -n (viết tắt của --lines) để giới hạn số dòng đầu ra. tail cũng có cờ -f (viết tắt của --follow) để in ra dữ liệu được nối thêm khi có dòng mới ở cuối tệp. Điều này đặc biệt tiện để theo dõi tệp log khi script đang chạy bằng tail -f <logfile>.

Kết hợp ký hiệu ống | với head và tail, bạn có thể trích xuất một số dòng nhất định từ giữa tệp nguồn và xuất nội dung ra tệp mẫu con. Ví dụ, để trích 20 dòng bắt đầu từ dòng 100:

$ head -n 120 adult.csv | tail -n 20 > adult_sample.csv

Lưu ý bạn trước hết lấy first_line + number_of_lines với head rồi tail lại number_of_lines. Lệnh trích mẫu tổng quát là:

$ head -n <total_lines> <source_file> | tail -n <number_of_lines> > <target_file>

trong đó total_lines = first_line + number_of_lines. Tuy nhiên, tệp mẫu mới của bạn không còn dòng tiêu đề. Ta đã biết cách thêm tiêu đề trở lại vào tệp mẫu bằng cat để nối tệp mẫu và tệp header.csv đã tạo trước đó:

$ cat adult_sample.csv header.csv > adult_sample_with_header.csv

Lưu ý bạn không dùng tên tệp nguồn adult\_sample.csv làm tên tệp đích, mà tạo một tệp mới tên adult\_sample\_with\_header.csv. Dùng cùng tên cho một tệp nguồn và tệp đích khi dùng cat sẽ dẫn đến nội dung tệp không như mong đợi. Tốt hơn là tạo một tệp mới làm đầu ra của lệnh cat.

Tìm bản ghi trùng bằng uniq

Với lệnh uniq bạn có thể tìm các dòng lặp kề nhau trong một tệp. uniq có một số cờ, hữu ích nhất gồm:

  • uniq -c: thêm số lần lặp vào mỗi dòng;
  • uniq -d: chỉ in các dòng trùng lặp; và
  • uniq -u: chỉ in các dòng duy nhất.

Tuy nhiên, uniq không thông minh. Các dòng trùng sẽ không được phát hiện nếu chúng không kề nhau. Điều đó có nghĩa là trước tiên bạn cần sort tệp. Lệnh này đếm số dòng trùng trong adult.csv.

$ sort adult.csv | uniq -d | wc -l
23

và cho thấy có 23 dòng trùng. Lệnh tiếp theo lấy đầu ra của tất cả các dòng kèm số lần lặp, sắp xếp ngược và in ra 3 dòng trùng xuất hiện nhiều nhất:

$ sort adult.csv | uniq -c | sort -r | head -n 3
3 25, Private, 195994, 1st-4th, 2, Never-married, ...
2 90, Private, 52386, Some-college, 10, Never-married, ...
2 49, Self-emp-not-inc, 43479, Some-college, 10, Married-civ-spouse, ...

Có rất nhiều lựa chọn mạnh mẽ khi kết hợp sort và uniq với các cờ khác nhau. Dùng man sort và man uniq để khám phá thêm.

Chọn cột với cut

Điểm hay của tệp CSV và lệnh shell là bạn có thể làm việc ở cấp độ cột bằng cách dùng cut để chọn một cột cụ thể. cut có hai cờ chính: -d để chỉ định dấu phân cách cột và -f để chỉ định các cột bạn muốn thao tác. Trong ví dụ sau, bạn dùng cut để tìm số lượng giá trị duy nhất của biến phân loại workclass (cột 2).

Trước tiên chọn cột workclass và pipe sang head để kiểm tra rằng bạn đã chọn đúng cột:

$ cut -d "," -f 2 adult.csv | head -3
workclass
State-gov
Self-emp-not-inc

Giờ, để đếm số giá trị duy nhất, bạn sắp xếp đầu ra của cut và pipe kết quả sang uniq -c, như sau:

$  cut -d "," -f 2 adult.csv | sort | uniq -c
1837
 960  Federal-gov
2093  Local-gov
   7  Never-worked
22696  Private
1116  Self-emp-inc
2541  Self-emp-not-inc
1298  State-gov
  14  Without-pay
   1 workclass

Kết quả cho biết, chẳng hạn, bạn có 1837 giá trị rỗng, và lớp chính áp đảo là Private với 22969 lần xuất hiện.

Dòng lệnh trên tương tự phương thức value_counts() áp dụng cho một Series trong DataFrame chứa dữ liệu adult.csv.

Vòng lặp

Đến đây, bạn chủ yếu làm việc trên một tệp. Để đổi tên, xử lý hoặc chuyển một số lượng lớn tệp, bạn nên thêm vòng lặp vào bộ công cụ shell của mình. Mẫu chung của một vòng lặp trong bash là:

while true; do
    _do something_ ;
done

Hãy áp dụng. Hình dung bạn có 1000 tệp với khoảng trắng trong tên tệp và bạn muốn thay mỗi khoảng trắng bằng dấu gạch dưới "_".

replace_source=' '
replace_target='_'
for filename in ./*.csv; do
    new_filename=${filename//$replace_source/$replace_target}
    mv "$filename" "$new_filename"
done

Trong ví dụ này,

  • Trước tiên bạn khai báo hai biến: replace_source và replace_target làm chỗ giữ cho ký tự khoảng trắng và dấu gạch dưới
  • Bạn lặp qua tất cả các tệp *.csv trong thư mục hiện tại
  • với mỗi filename, bạn tạo new_filename bằng cách thay mỗi khoảng trắng bằng dấu gạch dưới
  • và bạn đổi tên tệp từ tên hiện tại sang tên mới bằng lệnh mv

Thực tế, bạn không chỉ dùng shell để lặp qua các tệp trong thư mục mà còn tạo biến.

Biến

Bạn sẽ kết thúc phần giới thiệu lệnh shell với biến. Tạo biến trong shell rất đơn giản bằng

$ varname='<a string>'
$ varname=a number

Ví dụ:

$ varname='Hello world'
$ varname=123.4

Lưu ý không có khoảng trắng quanh dấu '='. var = '<a string>' sẽ không hoạt động. Để in giá trị biến, chỉ cần echo nó:

$ echo $varname
123.4

Và để dùng trong script, hãy bao nó trong dấu ngoặc kép như bạn đã thấy trước đó:

$ mv "$filename" "$new_filename"

Việc viết vòng lặp và dùng biến mở ra cánh cửa cho các thao tác tệp phức tạp hơn và là bước đệm đến viết script shell, vốn nằm ngoài phạm vi bài giới thiệu này. Tuy nhiên, viết script shell khá đơn giản nếu bạn bắt đầu từ những việc nhỏ và cải thiện dần khi tự tin hơn.

Kết luận

Các dòng lệnh shell cực kỳ hữu ích trong công việc hằng ngày của nhà khoa học dữ liệu. Còn rất nhiều ví dụ và tình huống sử dụng có thể khám phá. Và như bạn sẽ thấy, thường có nhiều cách để đạt cùng một kết quả bằng nhiều lệnh shell và cờ liên quan. Trong trường hợp đó, giữ mọi thứ đơn giản luôn là chiến lược thắng lợi.

Hy vọng các ví dụ ở đây sẽ giúp bạn tích hợp lệnh shell vào bộ công cụ xử lý dữ liệu của mình. Cứ liên hệ và chia sẻ mẹo shell với tôi trên Twitter: @alexip.

Xem thêm hướng dẫn Thiết lập Môi trường Khoa học Dữ liệu trên Máy tính của bạn.

Chủ đề

Liên quan