Phần II: Các cách để thực hiện cross-domain request

Với những hiểu biết cơ bản về Cross-domain request (Phần I), trong phần II này chúng ta sẽ tìm hiểu việc thực hiện Cross-domain request như thế nào ?

Dựa trên sự kiểm soát của bạn với server-side mà bạn có rất nhiều lựa chọn để cho phép Cross-Domain request tới Server-side của mình. Trong bài viết này, mình sẽ giới thiệu các bạn một số giải pháp: JSONP, cách sử dụng server-side proxy và CORS.

Lưu ý là vẫn còn các cách thức khác như phổ biến rộng rãi là kỷ thuật sử dụng Iframewindow.postMessage, tuy nhiên mình sẽ không đi sâu vào nó trong bài viết này.

Ok bắt đầu nào 😀

Đọc tiếp

Bài 4. Sự khác nhau giữa kiểu dữ liệu tham chiếu trong Java và con trỏ (pointer) trong C/C++ là gì? Và tại sao Java không dùng khái niệm con trỏ trong ngôn ngữ này?

Xin chào tất cả các bạn!

Sau đây tớ sẽ trình bày cho các bạn sự khác nhau giữa con trỏ trong C/C++ và biến tham chiếu trong Java và lý do tại sao Java không còn sử dụng con trỏ như C/C++ nữa. Nào, cùng bắt đầu nhé! 😛

1. Sự khác nhau:

  • Biến tham chiếu trong Java chỉ tham chiếu được đến vùng nhớ mà chương trình được cấp phát.
  • Con trỏ trong C/C++ có thể trỏ đến bất kỳ ô nhớ nào trong bộ nhớ (RAM).

Đọc tiếp

Bài 3. Sự khác nhau giữa kiểu dữ liệu cơ sở và kiểu dữ liệu tham chiếu trong ngôn ngữ Java là gì?

Xin chào tất cả các bạn!

Biến (variable) là một khái niệm cực kỳ quen thuộc với một developer, và đi kèm với nó chính là các kiểu dữ liệu. Ở bài trước, tớ đã cho các bạn thấy cách phân loại các kiểu dữ liệu trong Java. Và ở bài này, tớ sẽ cho các bạn thấy rõ hơn sự khác nhau giữa chúng qua các ví dụ cực kỳ chi tiết và hi vọng là dễ hiểu… 😛

Let’s go! 🙂


1. Kiểu:

  • Primitive Data Type (kiểu dữ liệu cơ sở): bao gồm các kiểu dữ liệu byte, short, int, long, float, double, boolean, char.
  • Reference Data Type (kiểu dữ liệu tham chiếu): bao gồm các kiểu dữ liệu còn lại.

2. Lưu trữ:

  • Primitive Data Type: Biến được lưu tại vùng nhớ stack, giá trị là các kiểu dữ liệu cơ sở.
  • Reference Data Type: Biến được lưu tại vùng nhớ stack, giá trị là địa chỉ của một đối tượng được lưu tại vùng nhớ heap.

Khi thực hiện việc gán giá trị, so sánh bằng toán tử ==, truyền tham số vào phương thức hay lấy dữ liệu trả về từ hàm thì giá trị của biến được truyền vào.

  • với biến là kiểu dữ liệu cơ sở giá trị của nó chính là giá trị mà đã được truyền cho nó
  • với biến kiểu dữ liệu tham chiếu, giá trị của nó là địa chỉ của một đối tượng nào đó, hoặc là null.

Để hiểu rõ hơn, chúng ta cùng đi qua các ví dụ bên dưới nào!!! 😛

3. Gán giá trị:

Khi thực hiện phép gán, giá trị của biến sẽ được copy đến biến mới.

  • Primitive Data Type:

Ví dụ:

int a = 10;

int b = a;

Sau khi thực thi 2 câu lệnh này, b sẽ có giá trị là 10. Xem hình minh họa:

int a = 10; a được gán giá trị là 10.

1

int b = a; b được gán giá trị của a (10).

2

  • Reference Data Type:

Ví dụ:

String a = new String("Java");

String b = a;

Sau khi thực hiện 2 câu lệnh này, biến tham chiếu b sẽ có giá trị là địa chỉ của đối tượng "Java". Xem hình minh họa:

String a = new String("Java"); Biến tham chiếu a có giá trị là địa chỉ của đối tượng "Java". (ở đây là 50000).3

String b = a; Giá trị 50000 của biến tham chiếu a được copy sang biến tham chiếu b ⇒ cả ab đều tham chiếu đến cùng một đối tượng "Java".4

4. So sánh bằng toán tử == :

Khi so sánh bằng toán tử ==, giá trị của biến sẽ được so sánh.

  • Primitive Data Type:

Ví dụ:

int a = 10;

int b = 10;

System.out.println(a == b);

Chương trình sẽ hiển thị kết quả là true.

Mô tả các bước thực thi chương trình:

int a = 10; a được gán giá trị là 105

int b = 10; b cũng được giá trị là 10

Khi so sánh (a == b) tương đương với so sánh (10 == 10) nên phép toán sẽ trả về true.

  • Reference Data Type:

Ví dụ:

String a = new String("Java");

String b = new String("Java");

System.out.println(a == b);

Chương trình sẽ hiển thị kết quả là false. Why?? Cùng đi nào!!! 😛

Ở đây do chúng ta sử dụng 2 lần toán tử new nên chương trình sẽ tạo ra 2 đối tượng "Java" trên vùng nhớ heap. Do đó, 2 biến tham chiếu ab sẽ tham chiếu đến 2 đối tượng "Java" khác nhau có địa chỉ lần lượt là 5000050008.

String a = new String("Java"); a có giá trị là 50000.6

String b = new String("Java"); b có giá trị là 50008.

Khi so sánh (a == b) tương đương với so sánh (50000 == 50008) nên phép toán sẽ trả về false.

5. Truyền tham số:

Khi truyền tham số vào một phương thức, chương trình sẽ copy giá trị đã truyền đó vào một biến khác có phạm vi trong phương thức đó để sử dụng. Xem ví dụ để dễ hiểu hơn nhé… :v

  • Primitive data type:

Ví dụ: Ta có chương trình:

public static void swap(int a, int b){

int temp = a;

a = b;

b = temp;

}

public static void main(String[] args){

int x = 10;

int y = 20;

swap(x, y);

System.out.println(x + "-" + y);

}

Sau khi thực thi chương trình, kết quả sẽ là 10-20.

Mô tả các bước thực thi chương trình:

Khi vào phương thức main(), chương trình sẽ tạo ra 1 stack frame (SF) và push nó vào vùng nhớ stack.7

int x = 10;

int y = 20;

2 biến trên được khai báo trong phương thức main() và là biến kiểu cơ sở nên nó sẽ được cấp phát trong SF main.

 

swap(x, y);8

Sau khi gọi phương thức swap, chương trình sẽ tạo ra một SF khác cho phương thức này và push nó vào vùng nhớ stack. Vì phương thức swap có 2 đối số là ab, nên khi gọi đến phương thức này nó sẽ tạo ra 2 biến ab tương ứng, sau đó nó sẽ copy giá trị của biến xy từ SF main vào biến ab của SF swap.

int temp = a;9

Sau khi thực hiện câu lệnh này, chương trình sẽ tạo ra một biến temp nằm trong SF swap (vì biến temp được khai báo trong phương thức swap). Sau đó nó sẽ copy giá trị của biến a sang biến temp.

 

 

a = b;10

Sau khi thực hiện câu lệnh này, chương trình sẽ copy giá trị của biến b vào biến a.

 

b = temp;11

tương tự, giá trị của biến temp sẽ được copy vào biến b.

 

 

 

 

 

Sau khi ra khỏi phương thức swap, SF swap được pop ra khỏi vùng nhớ stack, các biến a, b, temp đồng thời được giải phóng.12

System.out.println(x + "-“ + y);

 

Do vậy, sau khi thực hiện câu lệnh này, kết quả vẫn là 10-20.

 

  • Reference Data Type:

Ví dụ: Ta có chương trình:

public class MyInteger{

public int num;

public MyInteger(int num){

this.num = num;

}

}

public static void swap(MyInteger a, MyInteger b){

int temp = a.num;

a.num = b.num;

b.num = temp.num;

}

public static void main(String[] args){

MyInteger x = new MyInteger (10);

MyInteger y = new MyInteger (20);

swap(x, y);

System.out.println(x.num + "-" + y.num);

}

Sau khi thực thi chương trình, kết quả sẽ là 20-10. Tại sao lại như vậy? Cùng xem mô tả chi tiết bên dưới nhé…

Mô tả các bước thực thi chương trình:

Khi vào phương thức main, chương trình sẽ tạo ra 1 SF và push nó vào vùng nhớ stack.13

MyInteger x = new MyInteger (10);

MyInteger y = new MyInteger (20);

Chương trình sẽ cấp phát 2 đối tượng MyInteger trên vùng nhớ heap và 2 ô nhớ trên vùng nhớ stack để lưu trữ địa chỉ của 2 đối tượng trên. (Giống như con trỏ trên C/C++, các biến tham chiếu xy sẽ trỏ đến địa chỉ của các đối tượng chứa giá trị 1020 trên vùng nhớ heap)

 

swap(x, y);14

Khi phương thức swap được gọi, một SF mới được push vào vùng nhớ stack. Phương thức swap chứa các đối số a, b kiểu MyInteger (kiểu tham chiếu) nên trong SF swap sẽ cấp phát 2 biến a, b tương ứng (kiểu tham chiếu) và sau đó sẽ copy giá trị của các biến xy vào các biến ab.15

Các biến xy tham chiếu đến đối tượng 1020 trên vùng nhớ heap, nên sau khi copy, các biến ab cũng tham chiếu đến đối tượng 1020 đó. Tại thời điểm này, đối tượng 10 sẽ được tham chiếu bởi biến xa, còn đối tượng 20 sẽ được tham chiếu bởi biến yb.

 

int temp = a.num;16

Sau khi câu lệnh này thực hiện, chương trình sẽ tạo ra một biến temp trong SF swap (vì biến temp được khai báo trong phương thức swap) và copy giá trị num của đối tượng mà biến a đang tham chiếu vào biến temp.

 

a.num = b.num;17

Sau khi câu lệnh này thực hiện, chương trình sẽ copy giá trị num của đối tượng mà biến b đang tham chiếu đến vào biến num của đối tượng mà biến a đang tham chiếu đến.

 

b.num = temp.num;18

Sau khi câu lệnh này được thực hiện, chương trình sẽ copy giá trị của biến temp vào biến num của đối tượng mà biến b đang tham chiếu đến.

 

Sau khi ra khỏi phương thức swap, SF swap được pop ra khỏi vùng nhớ stack, các biến a, b, temp đồng thời được giải phóng.

System.out.println(x.num + "-" + y.num);19

Tại thời điểm này, biến x đang tham chiếu đến đối tượng có giá trị của num20y tham chiếu đến đối tượng có giá trị num10. Do đó, kết quả hiển thị sẽ là 20-10. Thật tuyệt đúng không nào? 😛

Ở trên là phương thức swap kinh điển đã được nhiều người dùng để ví dụ cho con trỏ trong C/C++ và bây giờ là biến tham chiếu trong Java có cơ chế tương tự như một biến con trỏ.

6. Giá trị trả về:

Tương tự như những phần trên, giá trị trả về của một phương thức chính là giá trị của biến được return.

  • Với các biến kiểu cơ sở, phương thức sẽ trả về giá trị mà biến đó đang chứa, một giá trị kiểu cơ sở.
  • Với các biến kiểu tham chiếu, phương thức cũng sẽ trả về giá trị mà biến đó đang chứa, nhưng giá trị bây giờ là một địa chỉ của một đối tượng mà biến đó đang tham chiếu đến.

 

Bạn học Java bao lâu rồi? Và bạn đã nắm vững được những kiến thức trên chưa? Hi vọng những kiến thức cơ bản này sẽ giúp các bạn giải thích được những “hiện tượng” mà lâu nay bạn chưa biết tại sao nó lại như thế.

Để biết rõ hơn về cách lưu trữ dữ liệu, các bạn vui lòng xem lại bài 1 và các kiểu dữ liệu thì xem lại bài 2 nhé. 😛

Bài 2. Các kiểu dữ liệu trong ngôn ngữ lập trình Java được phân loại như thế nào?

Xin chào tất cả các bạn!

Kiểu dữ liệu có lẽ là một vấn đề mà mới nghe mọi người thường nói: “Cơ bản thôi mà, có gì đâu mà phải nói nhiều…”. Có lẽ vậy, đúng là cơ bản thật. Đọc thử bài viết này và xem mình đã thực sự nắm rõ nó chưa nhé!

Bài viết dựa trên kiến thức và kinh nghiệm cá nhân, hi vọng sẽ chia sẻ những gì có ích và thiết thật nhất đến tất cả các bạn. Mong nhận được nhiều comment và phản hồi từ các bạn để các bài viết sau sẽ tốt hơn nữa.

Let’s go! 😛


 

Trong Java, các kiểu dữ liệu được chia thành 2 nhóm chính:

  1. Kiểu dữ liệu cơ sở (Primitive data type): bao gồm byte, short, int, long, float, double, boolean, char.
  2. Kiểu dữ liệu tham chiếu (Reference data type): tất cả các kiểu còn lại như int[], String, Scanner,… (như vậy, class được xem là một kiểu dữ liệu tham chiếu đấy nhé).

Sau đây, để hiểu rõ tại sao ngôn ngữ Java lại phân các kiểu dữ liệu thành 2 nhóm như vậy, chúng ta hãy cùng xem cách thức tổ chức lưu trữ của 2 nhóm này trên memory như thế nào nhé! 🙂

  1. Kiểu dữ liệu cơ sở: Tất cả các biến thuộc kiểu dữ liệu cơ sở đều được lưu trên vùng nhớ stack.

Ví dụ: Chúng ta có câu khai báo như sau:

int a = 10;

Nào, chúng ta cùng chia nhỏ câu lệnh trên để các bạn dễ hình dung các bước mà chương trình sẽ thực hiện:

  • int a: Cấp phát một ô nhớ gồm 4 bytes trên vùng nhớ stack. (Vì độ dài của kiểu int là 4 bytes)4
  • a = 10: Gán giá trị 10 cho ô nhớ trên.5
  • Như vậy, biến a chính là một ô nhớ trên vùng nhớ stack. Đơn giản và dễ hiểu đúng không nào các bạn. Vậy còn kiểu dữ liệu tham chiếu sẽ được lưu trữ thế nào nhỉ? Cùng đi tiếp nào… 😛
  1. Kiểu dữ liệu tham chiếu: những biến thuộc kiểu dữ liệu tham chiếu (hay biến tham chiếu) sẽ được lưu tại vùng nhớ stack và đối tượng sinh ra (sau toán tử new) sẽ được lưu tại vùng nhớ heap. Giá trị của biến tham chiếu chính là địa chỉ của đối tượng được sinh ra đó.

Ví dụ: Chúng ta có câu khai báo như sau:

String a = new String(“Java”);

Tương tự ở trên, chúng ta cùng chia nhỏ câu lệnh trên dể dễ hình dung các bước mà chương trình sẽ thực thi trong câu lệnh đó nhé!

  • Đầu tiên là String a: Cấp phát một ô nhớ trên vùng nhớ stack, ô nhớ này chính là biến tham chiếu a.6
  • new String(): Cấp phát một ô nhớ trên vùng nhớ heap, ô nhớ này là một đối tượng kiểu String, việc cấp ô nhớ này do toán tử new thực hiện.7
  • String("Java"): Gán giá trị “Java” cho ô nhớ trên vùng nhớ heap.8
  • a = "Java": Gán địa chỉ của đối tượng trên vùng nhớ heap cho ô nhớ trên vùng nhớ stack (biến tham chiếu a).8

Như ví dụ trên, ta thấy biến tham chiếu a tương tự như con trỏ (pointer) trong ngôn ngữ C/C++, nó được lưu tại vùng nhớ stack và tham chiếu đến địa chỉ của một đối tượng được tạo ra trên vùng nhớ heap.

Vậy sự khác nhau của con trỏ (pointer) trong ngôn ngữ C/C++ và biến tham chiếu (reference variable) trong ngôn ngữ Java là gì? Tại sao Java lại không sử dụng con trỏ như C/C++ nữa? Và có thực sự là Java đã loại bỏ hoàn toàn con trỏ trong ngôn ngữ này?  Hãy đón đọc trong những bài viết tiếp sau các bạn nhé! 🙂

Để biết rõ hơn về vùng nhớ stack và heap, các bạn vui lòng xem lại bài đầu tiên của series này nhé.

Bài 1. Bạn đã biết cách tổ chức lưu trữ dữ liệu của một chương trình Java trên bộ nhớ máy tính?

Xin chào tất cả các bạn!

Tớ là Anh Sắn và sau đây là series bài viết về những thứ căn bản nhất của lập trình Java được đúc kết từ kiến thức và kinh nghiệm mà tớ đã học hỏi được. Hi vọng những bài viết này sẽ mang đến cái nhìn rõ ràng và dễ hiểu nhất cho các bạn. Rất mong nhận được sự góp ý, thảo luận chân thành từ các bạn để những bài viết sau sẽ trở nên tốt hơn với tớ và các bạn. 😛


Để bắt đầu cho series này, tớ sẽ đi đến phần căn bản nhất, đó là cách mà một chương trình Java lưu trữ dữ liệu trên bộ nhớ máy tính.

Khi một chương trình Java được thực thi, nó sẽ yêu cầu hệ điều hành cấp phát một không gian trên bộ nhớ để lưu trữ toàn bộ dữ liệu và thông tin của nó.

Sau đó, nó sẽ chia vùng không gian đó thành 4 vùng nhớ (memory segment) để lưu trữ. Để dễ hiểu thì các bạn xem hình ảnh bên dưới nhé 🙂

1

Đọc tiếp

Chủ đề 1: NÊN HIỂU CON TRỎ TRONG C/C++ NHƯ THẾ NÀO ?

Chào các bạn !

Đã lâu lắm rồi tôi mới được có dịp gõ gõ trở lại. Thời gian qua blog Tôi Thắc Mắc đã nhận rất nhiều đóng góp và câu hỏi gửi về. Sau một lúc chọn lọc thì chúng tôi thấy rằng vấn đề mà các bạn vướn mắc , đại đa số vẫn là vấn đề của những bạn newbie. Cụ thể, đó là những khó khăn mà các bạn gặp phải khi bước đầu làm quen với lập trình. Chúng tôi thực sự thấu hiểu cảm giác đó. Vì vậy trong các seri tới đây, chúng tôi sẽ tập trung viết về những kiến thức , chủ đề cơ bản nhất để các bạn từng bước lấy lại căn bản.

Vì đại đa số các bạn mới nhập môn đều làm quen với C/C++ . Nên xin phép chúng tôi sẽ bắt đầu với loạt bài viết về chủ đề này. Đùa chứ theo quan điểm của chúng tôi, nếu các bạn thực sự hiểu được các nền tảng cơ bản nhất của C/C++ thì sẽ rất dễ dàng cho các bạn khi tiếp cận với các ngôn ngữ khác nhau này. Ok ! Luyên thuyên vậy đủ rồi … Chúng ta chiến thôi !

Chủ đề 1: NÊN HIỂU CON TRỎ TRONG C/C++ NHƯ THẾ NÀO ?

“Vâng, đây là một chủ đề luôn là chủ đề nhức nhối của nhức nhối ! “ – Anh Khoai

Đầu tiên, chúng ta cần hiểu :

Đọc tiếp

Một số câu lệnh trong Unix liên quan đến phân quyền

Trước khi đọc bài này, các bạn hãy xem qua các phân quyền của file và thư mục được nói đến ở bài này nhé! Trong bài viết này, tớ sẽ tiếp tục nói về các phân quyền nhưng mà tập trung vào các câu lệnh liên quan đến chúng.

Cách thay đổi phân quyền cho file và thư mục

Để thay đổi phân quyền của file và thư mục, các bạn có thể sử dụng lệnh chmod (change mode) theo các cách sau:

  • chmod [ugoa]+[rwx] filename, trong đó ‘u‘ chỉ người dùng quản lý file/thư mục (user), ‘g‘ chỉ nhóm quản lý file/thư mục (group), ‘o‘ chỉ những người dùng khác (others), ‘a‘ chỉ tất cả các người dùng (all). Câu lệnh này dùng để thêm quyền cho một hay nhiều loại người dùng đối với file/thư mục nào đó. Để xóa quyền, ta dùng chmod [ugoa]-[rwx] filename. Để gán trực tiếp phân quyền, ta dùng chmod [ugoa]=[rwx] filename.

    chmod -c o+r myfile
    Thêm quyền đọc file myfile cho những người dùng khác. (-c để liệt kê các thay đổi về phân quyền của file myfile)
    chmod -c ug+rw *.sh
    Thêm quyền đọc và thực thi tất cả các file có đuôi sh cho người dùng quản lý và nhóm người dùng (-c liệt kê các thay đổi về phân quyền)
    chmod -c a=r *
    Tất cả các người dùng chỉ có quyền đọc tất cả các file và thư mục
    chmod -c ugo-w *.txt
    Bỏ đi quyền đọc các file có đuôi txt đối với tất cả các người dùng. Tương đương với chmod -c a-w *.txt

  • Chúng ta cũng có thể dùng 3 số thập phân để gán phân quyền cho mỗi loại người dùng (mỗi số cho u, g, o). Mỗi số thập phân có giá trị từ 0 đến 7, dùng để biểu diễn một dãy gồm 3 bit (bbb), tương ứng với rwx. Bit 0 nghĩa là có, bit 1 nghĩa là không có. Ví dụ: 1 (001) tương ứng với x, 2 (010) tương ứng với w, 4 (100) tương ứng với r, 5 (101) tương ứng với rx. Khi kết hợp 3 số thập phân, ta sẽ có: “345” tương ứng với “-wxr–r-x”, “700” tương ứng với “rwx——“.

    chmod -c 321 file.txt
    321 = 011 010 001 = -wx -w- --x
    chmod -c 400 * 400 = 100 000 000 = r-- --- ---

  • Một số cách khác, chẳng hạn:

    chmod -Rc 600 mydir
    Thay đổi phân quyền của thư mục mydir và tất cả các file và thư mục bên trong thư mục mydir thành rw------- (-R viết tắt của recursive)
    chmod --reference temp.sh *.sh
    Sử dụng thuộc tính phân quyền của file temp.sh để gán cho tất cả các file có đuôi sh

Một số câu lệnh liên quan đến phân quyền của thư mục

Trước hết, hãy làm rõ thư mục trong unix là gì? Nó không đơn thuẩn là một chỗ chứa các file và folder như chúng ta thường thấy. Thực ra, một thư mục còn mang thông tin của các file và thư mục con bên trong nó.
directoryinfo
Thư mục sẽ chứa danh sách các cặp (inode, tên file/thư mục). Mỗi cặp là thông tin của một file/thư mục. inode đây là gì? Trong inode sẽ chứa các thuộc tính của một file/thư mục con. Các bạn có thể thấy một số thuộc tính trong hình ảnh bên dưới.
ex
Bây giờ, có thể nói về một số câu lệnh rồi. 🙂

  • Lệnh ls và phân quyền ‘r‘:
    Lệnh ls chỉ thực thi với thư mục có phân quyền ‘r‘. Phân quyền ‘rchỉ cho phép liệt kê tên của các file/thư mục con. Khi gọi ls, nó sẽ hiển thị tên các file/thư mục con bên trong. Khi gọi ls -l, nó sẽ hiển thị tên và inode của các file/thư mục con, như ở hình trên, nhưng trong trường hợp thư mục chỉ có phân quyền ‘r‘ thì thông tin của inode sẽ không đọc được.
  • Lệnh cd và phân quyền ‘x‘:
    Lệnh cd chỉ thực thi với thư mục có phân quyền ‘x‘. Phân quyền ‘x‘ cho phép truy cập vào một thư mục. Khi gọi cd đường dẫn đến thư mục, thì ta sẽ được chuyển đến thư mục đó. Việc truy cập vào một thư mục và liệt kê nội dung thư mục là khác nhau. Trong câu lệnh sau:

    ls d1/d2/d3
    Bắt đầu từ thư mục hiện tại, thư mục con d1 cần phân quyền 'x' để truy cập vào, thư mục d2 cần phân quyền 'x' để truy cập, và cuối cùng thư mục d3 cần phân quyền 'r' để lệnh ls có thể chạy được.

    Như vậy, việc truy cập chỉ là bước trung gian. Chúng ta có thể truy cập một thư mục mà không cần phải biết thông tin của file/thư mục con của nó. Một số ví dụ:

    chmod 700 d1/d2/d3/d4
    Câu lệnh này thay đổi thuộc tính phân quyền cho thư mục d4. Các thư mục d1, d2 và d3 cần phân quyền 'x'. Không ràng buộc gì với thư mục d4.
    cd d1/d2/d3
    Tất cả thư mục d1, d2 và d3 đều cần đến phân quyền 'x'

  • Phân quyền ‘w‘ có lẽ là phân quyền dễ hiểu nhất. Nếu không có phân quyền này, chúng ta sẽ không thể thêm/xóa file/thư mục con, hay sửa nội dung của file được. Đơn giản vậy thôi 😀

Một số câu lệnh liên quan đến phân quyền của file

Phần này liên quan đến một số câu lệnh thao tác với nội dung của file, chẳng hạn như:

  • touch filename, gedit filename, dùng để tạo file
  • gedit filename, dùng để mở file và sửa nội dung của file.
  • more filename, less filename, dùng để đọc và hiển thị nội dung của file ra màn hình.
  • Chạy một file thực thi (file có phân quyền ‘x‘), bạn có thể xem bài viết này

Các bạn có thể kết hợp chmod với các câu lệnh trên để xem các phân quyền ảnh hưởng như thế nào. Do làm việc với file cũng khá đơn giản, nên tớ xin để dành cho bạn đọc.
Trên đây là cách hiểu của mình về sự liên hệ giữa phân quyền và câu lệnh trong Unix. Có thể bài viết có phần dài dòng, và một chút lủng củng. Do vậy, rất mong sự góp ý của các bạn. 🙂
Chúc các bạn học tốt. 😀

Phân biệt các quyền truy cập của file và thư mục trong Unix

Mình xin bắt đầu bài viết bằng một câu lệnh khá quen thuộc, dùng để liệt kê các file và thư mục cùng với tất cả thuộc tính của chúng.

ls -l

Câu lệnh in ra màn hình rất nhiều dòng. Mỗi dòng tương ứng với một file hoặc thư mục có trong thư mục hiện tại.
...
-rw-rw-r-- 1 zuizui zuizui 0 Th03 15 11:10 test.txt
...

Vậy ý nghĩa của các thành phần trong một dòng là gì?
entryLS
Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về phân quyền của file và thư mục đối với từng loại user.
Trước hết, hãy xem lại hình ảnh, ta thấy:

  • Kí tự đầu tiên cho biết đó là file (-) hay thư mục (d). Trong hình, chính là file. Ngoài ra còn có các ký hiệu khác như: l (link), s (socket), v.v…
  • Tiếp theo là 3 vùng phân quyền. Vùng phân quyền này luôn gồm 9 kí tự, được chia thành 3 nhóm như trong hình. Mỗi nhóm gồm 3 kí tự, luôn được viết theo thứ tự rwx.

Vậy các kí tự này là gì?

  1. Đối với file (được kí hiệu bằng ‘‘):
    • r cho phép đọc file.
    • w cho phép ghi file.
    • x cho thấy đây là chương trình thực thi.
  2. Đối với thư mục (được kí hiệu bằng ‘d‘):
    • r cho phép liệt kê các file và thư mục con.
    • w cho phép tạo file mới và thư mục con mới.
    • x cho phép truy cập vào thư mục.

Trong thuộc tính phân quyền của file, ngoài 3 kí tự ‘r’, ‘w’, ‘x’ thì còn có kí tự ‘‘. Kí tự này dùng để thay thế cho ‘r’, ‘w’ hoặc ‘x’, ám chỉ 1 file hoặc thư mục không có quyền ‘r’, ‘w’ hoặc ‘x’.
Một số ví dụ để dễ hiểu hơn nhé 😉

-r-xr----- user1 group1 my_file
Đây là file my_file.
Người dùng user1 có quyền đọc và thực thi file.
Nhóm group1 có quyền đọc file.
Người dùng khác không có quyền gì cả.

drwxr-xr-- user2 group2 my_dir
Đây là thư mục my_dir.
Người dùng user2 có quyền liệt kê, thay đổi và truy cập nội dung thư mục.
Nhóm group2 có quyền liệt kê và truy cập nội dung thư mục.
Người dùng khác chỉ có quyền liệt kê nội dung thư mục.

-rw------- user3 group3 your_file
Đây là file your_file.
Người dùng user3 có quyền đọc và ghi file.
Nhóm group3 và các người dùng khác không có quyền gì cả.

Hi vọng sau những ví dụ này, các bạn có thể hiểu được ý nghĩa của rwx đối với file và thư mục là như thế nào.

Cách gọi chương trình thực thi trong Unix

Trong Unix, khi gọi một chương trình thực thi (chương trình được phân quyền ‘x’, xem thêm bài viết này để dễ hiểu hơn) trong thư mục hiện tại thông qua Terminal, bạn cần phải thêm “./” ở trước tên chương trình. Chẳng hạn, khi biên dịch một chương trình C++, bằng cách dùng lệnh:

g++ main.cpp

Sẽ tạo ra 1 file chương trình là a.out. Để chạy file a.out này, ta gõ lệnh:

./a.out

Vậy “./” bắt nguồn từ đâu?
Trước hết, trong Unix, có 1 biến môi trường tên là PATH. PATH chứa danh sách các thư mục dùng cho việc tìm kiếm chương trình thực thi. Chẳng hạn g++ ở ví dụ trên, khi được dùng trong Terminal, Unix sẽ tìm kiếm chương trình đó trong danh sách các thư mục trong PATH. Khi tìm thấy, nó sẽ thực hiện chương trình. Nếu không, nó sẽ hiện ra lỗi bash: xxxx: command not found. Để xem biến PATH, có thể dùng lệnh:

echo $PATH

Danh sách các thư mục trong biến PATH được cách nhau bằng dấu hai chấm (:)
Unix chỉ tìm kiếm tên chương trình trong danh sách các thư mục được liệt kê trong PATH mà không tìm kiếm trong thư mục hiện tại. Do đó, ta cần phải chỉ định cụ thể thư mục hiện tại khi gọi chương trình trong Unix. Xem lại “./a.out”, ta thấy . chỉ thư mục hiện tại, a.out chỉ tên chương trình thực thi.
Vậy có cách nào để chỉ cần gọi “a.out” thôi hay không?
Đơn giản chỉ cần đưa thư mục hiện tại vào biến PATH trước khi chạy chương trình. Bằng cách dùng lệnh:

export PATH=.:$PATH

Các bạn lưu ý là lệnh export chỉ thay đổi giá trị của biến PATH trong Terminal hiện tại. Khi thoát khỏi Terminal, biến PATH sẽ mang giá trị như cũ. Vậy cách nào để thay đổi vĩnh viễn giá trị của biến PATH? Có 2 hướng:

  • Nếu chỉ muốn thay đổi giá trị của biến PATH cho User hiện tại, thêm câu lệnh export ở trên vào cuối file “~/.bash_profile” hoặc “~/.profile”.
  • Nếu muốn thay đổi biến PATH cho toàn bộ các User, thêm câu lệnh export ở trên vào cuối file “/etc/profile”.

Tóm lại, tất cả các chương trình thực thi đều bắt nguồn từ biến PATH trong Unix.
Xin mở rộng một chút, riêng trong Windows thì khi gọi chương trình trong CMD. Windows sẽ tự động tìm kiếm ở thư mục hiện tại trước mà không cần phải thêm “./” ở đầu. Nếu không tìm thấy, nó sẽ tìm kiếm trong danh sách thư mục trong biến PATH.
Bên cạnh biến PATH thì trong Unix và Windows còn nhiều biến môi trường khác. Các bạn có thể tìm hiểu thêm. Chúc các bạn học tốt. 😀