Tại sao awk dừng lại và chờ đợi nếu tên tệp chứa = và cách xử lý xung quanh đó?


Câu trả lời:


19

Như Chris nói , các đối số của biểu mẫu variablename=anythingđược coi là phép gán biến (được thực hiện tại thời điểm các đối số được xử lý trái ngược với các đối số (mới hơn) -v var=valueđược thực hiện trước các BEGINcâu lệnh) thay vì tên tệp đầu vào.

Điều đó có thể hữu ích trong những việc như:

awk '{print $1}' FS=/ RS='\n' file1 FS='\n' RS= file2

Nơi bạn có thể chỉ định một tập tin khác nhau FS/ RSmỗi. Nó cũng thường được sử dụng trong:

awk '!file1_processed{a[$0]; next}; {...}' file1 file1_processed=1 file2

Phiên bản nào an toàn hơn:

awk 'NR==FNR{a[$0]; next}; {...}' file1 file2

(không hoạt động nếu file1trống)

Nhưng điều đó cản trở khi bạn có các tệp có tên chứa các =ký tự.

Bây giờ, đó chỉ là một vấn đề khi cái còn lại của cái đầu tiên =là một awktên biến hợp lệ .

Cái gì tạo thành một tên biến hợp lệ trong awkchặt chẽ hơn trong sh.

POSIX yêu cầu nó phải giống như:

[_a-zA-Z][_a-zA-Z0-9]*

Chỉ với các ký tự của bộ ký tự di động. Tuy nhiên, /usr/xpg4/bin/awkít nhất Solaris 11 không tuân thủ về vấn đề đó và cho phép bất kỳ ký tự chữ cái nào trong miền địa phương trong tên biến, không chỉ a-zA-Z.

Vì vậy, một cuộc tranh cãi như x+y=foohay =barhay ./foo=barvẫn đối xử như một tên tập tin đầu vào và không phải là một nhiệm vụ như những gì còn lại của người đầu tiên =không phải là một tên biến hợp lệ. Một đối số như Stéphane=Chazelas.txtcó thể hoặc không, tùy thuộc vào việc awkthực hiện và ngôn ngữ.

Đó là lý do tại sao với awk, nên sử dụng:

awk '...' ./*.txt

thay vì

awk '...' *.txt

ví dụ để tránh sự cố nếu bạn không thể đảm bảo tên của txttệp sẽ không chứa =ký tự.

Ngoài ra, hãy cẩn thận rằng một đối số như -vfoo=bar.txtcó thể được coi là một tùy chọn nếu bạn sử dụng:

awk -f file.awk -vfoo=bar.txt

(cũng áp dụng awk '{code}' -vfoo=bar.txtvới các awkphiên bản busybox trước 1.28.0, xem báo cáo lỗi tương ứng ).

Một lần nữa, sử dụng ./*.txtcác công trình xung quanh đó (sử dụng một ./tiền tố cũng giúp với một tập tin gọi -mà nếu không awkhiểu như nghĩa đầu vào tiêu chuẩn thay vì).

Đó cũng là lý do tại sao

#! /usr/bin/awk -f

shebangs không thực sự làm việc. Trong khi var=valuenhững cái có thể được xử lý bằng cách sửa các ARGVgiá trị (thêm ./tiền tố) trong một BEGINcâu lệnh:

#! /usr/bin/awk -f
BEGIN {
  for (i = 1; i < ARGC; i++)
    if (ARGV[i] ~ /^[_[:alpha:]][_[:alnum:]]*=/)
      ARGV[i] = "./" ARGV[i]
}
# rest of awk script

Điều đó sẽ không giúp với các tùy chọn như những cái được nhìn thấy awkvà không phải là awkkịch bản.

Một vấn đề thẩm mỹ tiềm năng khi sử dụng ./tiền tố đó là nó kết thúc FILENAME, nhưng bạn luôn có thể sử dụng substr(FILENAME, 3)để loại bỏ nó nếu bạn không muốn.

Việc triển khai GNU awkkhắc phục tất cả những vấn đề đó với -Etùy chọn của nó .

Sau đó -E, gawk chỉ mong đợi đường dẫn của awktập lệnh (trong đó -vẫn có nghĩa là stdin) và sau đó là danh sách các đường dẫn tệp đầu vào (và ở đó, thậm chí không -được xử lý đặc biệt).

Nó được thiết kế đặc biệt cho:

#! /usr/bin/gawk -E

shebang nơi danh sách các đối số luôn là các tệp đầu vào (lưu ý rằng bạn vẫn có thể tự do chỉnh sửa ARGVdanh sách đó trong một BEGINcâu lệnh).

Bạn cũng có thể sử dụng nó như:

gawk -e '...awk code here...' -E /dev/null *.txt

Chúng tôi sử dụng -Evới một tập lệnh trống ( /dev/null) chỉ để đảm bảo những tập lệnh *.txtsau luôn được coi là tập tin đầu vào, ngay cả khi chúng có chứa các =ký tự.


Tôi không thấy cách đường dẫn rõ ràng kết thúc trong FILENAME là một vấn đề. Dù kịch bản awk là nói chung, trong trường hợp này nó sẽ xử lý tất cả các loại đường dẫn kết thúc trong FILENAME (bao gồm nhưng không giới hạn ../foo, /path/to/foovà đường dẫn mà đang ở trong một mã hóa khác nhau) - trong trường hợp này substr(FILENAME,3)sẽ không đủ, hoặc nó một kịch bản một cảnh quay mà người dùng về cơ bản biết tên tập tin là gì - trong trường hợp đó có lẽ anh ta không nên bận tâm với bất kỳ ai trong số họ có chứa =;-)
mosvy 23/12/18

2
@mosvy Tôi không nghĩ rằng nó có quá nhiều ./vấn đề, nhưng nó có thể không mong muốn trong một số điều kiện nhất định, chẳng hạn như các trường hợp tên tệp phải được đưa vào đầu ra, trong trường hợp ./đó là dư thừa và không cần thiết, vì vậy bạn Sẽ cần phải thoát khỏi nó bằng cách nào đó. Dưới đây là ít nhất một ví dụ . Đối với người dùng biết tên tệp là gì - tốt, trong trường hợp này, chúng tôi cũng biết tên tệp là gì, nhưng =vẫn có cách xử lý đúng. Vì vậy, có thể dẫn đầu -có được trong cách.
Sergiy Kolodyazhnyy

@mosvy, vâng, ý tưởng là bạn muốn sử dụng ./tiền tố để làm việc xung quanh awktính năng (mis) đó nhưng sau đó bạn kết thúc với một ./đầu ra mà bạn có thể muốn loại bỏ. Xem cách kiểm tra xem dòng đầu tiên của tệp có chứa một chuỗi cụ thể không? làm ví dụ
Stéphane Chazelas

Không chỉ là cục bộ (liên quan đến thư mục này) ./mà còn là toàn cục (đường dẫn tuyệt đối) /làm cho awk diễn giải đối số dưới dạng tệp.
Isaac

21

Trong hầu hết các phiên bản của awk, các đối số sau khi chương trình thực thi là:

  1. Một tập tin
  2. Một bài tập của mẫu x=y

Vì tên tệp của bạn đang được hiểu là trường hợp # 2, awk vẫn đang chờ một cái gì đó để đọc trên stdin (vì nó không nhận thấy rằng đã có bất kỳ tên tệp nào được thông qua).

Có thể, hành vi này được ghi lại trong POSIX :

Một trong hai loại đối số sau đây có thể được trộn lẫn với nhau:

  • tệp: Tên đường dẫn của tệp chứa đầu vào cần đọc, được khớp với nhóm mẫu trong chương trình. Nếu không có toán hạng tệp nào được chỉ định hoặc nếu toán hạng tệp là '-', thì đầu vào tiêu chuẩn sẽ được sử dụng.
  • gán: Một toán hạng bắt đầu bằng ký tự gạch dưới hoặc chữ cái từ bộ ký tự di động (xem bảng trong tập Định nghĩa cơ sở của IEEE Std 1003.1-2001, Phần 6.1, Bộ ký tự di động), theo sau là một chuỗi các dấu gạch dưới, chữ số, và bảng chữ cái từ bộ ký tự di động, theo sau là ký tự '=', sẽ chỉ định một phép gán biến thay vì tên đường dẫn.

Như vậy, có thể, bạn có một vài lựa chọn (# 1 có thể là ít xâm phạm nhất):

  1. Sử dụng awk ... ./my=file, điều này vượt qua điều này vì .không phải là "ký tự gạch dưới hoặc chữ cái trong bộ ký tự di động".
  2. Đặt tệp trên stdin bằng cách sử dụng awk ... < my=file. Tuy nhiên, điều này không hoạt động tốt với nhiều tập tin.
  3. Tạo một liên kết cứng đến tệp tạm thời và sử dụng nó. Bạn có thể làm một cái gì đó như ln my=file my_file, và sau đó sử dụng my_filenhư bình thường. Không sao chép sẽ được thực hiện và cả hai tệp sẽ được hỗ trợ bởi cùng một dữ liệu và siêu dữ liệu inode. Sau khi sử dụng, an toàn để xóa liên kết được tạo vì số lượng tham chiếu đến nút vẫn sẽ lớn hơn 0.

6
Không ./my=file hoạt động? % awk 'processing_script_here' ./my=file.txt awk: fatal: cannot open file ./my=file.txt' for reading (No such file or directory). Đây phải là di động vì ./mykhông phải là tên biến hợp lệ, vì vậy không nên phân tích cú pháp theo cách đó.
Stephen Harris

2
Như văn bản POSIX nói, vấn đề chỉ xảy ra khi ký tự đầu tiên =đứng trước ký tự gạch dưới hoặc chữ cái từ bộ ký tự di động (xem bảng trong tập Định nghĩa cơ sở của IEEE Std 1003.1-2001, Phần 6.1, Bộ ký tự di động), theo sau là một chuỗi các dấu gạch dưới, chữ số và bảng chữ cái từ bộ ký tự di động . do đó, một đường dẫn tập tin như ++foo=bar.txthoặc =foohoặc ./foo=barlà tất cả OK như rằng .hoặc +không phải là một [_a-zA-Z].
Stéphane Chazelas

1
@SergiyKolodyazhnyy awk nằm ngoài vỏ, vì vậy bạn không sử dụng nó. ./my=filesẽ được chuyển qua nguyên văn.
Chris Xuống

1
@SergiyKolodyazhnyy, tương tự cho awk '{print $1,$2}' /etc/passwd. Vấn đề là việc shell mở tệp trái ngược với awk sẽ không tạo ra bất kỳ sự khác biệt nào về việc liệu nó có thể tìm kiếm được hay không. Trên thực tế, trong awk '{exit}' < /etc/passwd, bạn sẽ awktìm cách quay lại phần cuối của bản ghi đầu tiên exitđể đảm bảo rằng nó rời khỏi vị trí trong stdin ở đó. POSIX yêu cầu điều đó. /usr/xpg4/bin/awkthực hiện trên Solaris, nhưng dường như gawkcũng không mawklàm điều đó trên GNU / Linux.
Stéphane Chazelas

3
@mosvy, xem phần INPUT PHIM tại pubs.opengroup.org/onlinepub/9699919799/utilities/ trộm Nó hữu ích trong một số mẫu sử dụng chỉ có ý nghĩa với các tệp thông thường như khi bạn muốn cắt bớt tệp hoặc ghi dữ liệu vào một vị trí được xác định theo awkcách đó.
Stéphane Chazelas

3

Để trích dẫn tài liệu gawk (lưu ý nhấn mạnh thêm):

Bất kỳ đối số bổ sung nào trên dòng lệnh thường được coi là tệp đầu vào được xử lý theo thứ tự được chỉ định. Tuy nhiên, một đối số có dạng var = value, gán giá trị giá trị cho biến var, nó không chỉ định một tệp nào cả.

Tại sao lệnh dừng lại và chờ đợi? Bởi vì trong biểu mẫu awk 'processing_script_here' my=file.txt không có tệp nào được định nghĩa theo định nghĩa trên - my=file.txtđược hiểu là phép gán biến và nếu không có tệp nào được xác định awksẽ đọc stdin (cũng hiển nhiên từ straceđó cho thấy awk trong lệnh đó đang chờ trên read(0,'...)tòa nhà.

Điều này cũng được ghi lại trong thông số kỹ thuật POSIX awk , xem phần OPERANDS và phần bài tập trong đó)

Phân công biến là hiển nhiên trong awk '{print foo}' foo=bar /etc/passwd giá trị đó foođược in cho mỗi dòng trong / etc / passwd. Chỉ định ./foo=barhoặc đường dẫn đầy đủ tuy nhiên không hoạt động.

Lưu ý rằng chạy stracetrênawk '1' foo=bar cũng như kiểm tra cat foo=barcho thấy đây là vấn đề cụ thể của awk và execve không hiển thị tên tệp là đối số được truyền, vì vậy shell không liên quan gì đến các phép gán biến env trong trường hợp này.

Ngoài ra, xin lưu ý rằng awk '...script...' foo=barsẽ không gây ra việc tạo biến môi trường bằng shell, vì các phép gán biến môi trường phải có trước một lệnh để có hiệu lực. Xem Quy tắc ngữ pháp POSIX Shell , điểm số 7. Ngoài ra, điều này có thể được xác minh thông quaawk '{print ENVIRON["foo"]}' foo=bar /etc/passwd

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.