Khi nào tôi có thể sử dụng IFS tạm thời để tách trường?


19

Trong bash, nói rằng bạn có var=a.b.c., sau đó:

$ IFS=. printf "%s\n" $var
a.b.c

Tuy nhiên, việc sử dụng như vậy IFSsẽ có hiệu lực trong khi tạo một mảng:

$ IFS=. arr=($var)
$ printf "%s\n" "${arr[@]}"
a
b
c

Điều này rất thuận tiện, chắc chắn, nhưng tài liệu này ở đâu? Việc đọc nhanh các phần trên Mảng hoặc Chia tách từ trong tài liệu Bash không đưa ra bất kỳ dấu hiệu nào. Tìm kiếm IFSthông qua tài liệu một trang không cung cấp bất kỳ gợi ý nào về hiệu ứng này.

Tôi không chắc chắn khi nào tôi có thể làm một cách đáng tin cậy:

IFS=x do something

Và mong đợi điều đó IFSsẽ ảnh hưởng đến việc chia tách trường.

Câu trả lời:


28

Ý tưởng cơ bản là VAR=VALUE some-commandthiết lập VARđể VALUEthực hiện some-commandkhi nào some-commandlà một lệnh bên ngoài và nó không được ưa thích hơn thế. Nếu bạn kết hợp trực giác này với một số kiến ​​thức về cách thức hoạt động của một chiếc vỏ, bạn nên đưa ra câu trả lời đúng trong hầu hết các trường hợp. Tham chiếu POSIX là các lệnh đơn giản của Hồi giáo trong chương Chương trình ngôn ngữ lệnh Shell Shell .

Nếu some-commandlà một lệnh bên ngoài , VAR=VALUE some-commandtương đương với env VAR=VALUE some-command. VARđược xuất trong môi trường some-commandvà giá trị của nó (hoặc thiếu giá trị) trong vỏ không thay đổi.

Nếu some-commandlà một hàm , thì VAR=VALUE some-commandtương đương với VAR=VALUE; some-command, tức là phép gán vẫn giữ nguyên sau khi hàm đã trả về và biến không được xuất ra môi trường. Lý do cho điều đó liên quan đến thiết kế của vỏ Bourne (và sau đó là khả năng tương thích ngược): nó không có cơ sở để lưu và khôi phục các giá trị biến xung quanh việc thực thi hàm. Không xuất biến có ý nghĩa vì một hàm thực thi trong chính shell. Tuy nhiên, ksh (bao gồm cả ATT ksh93 và pdksh / mksh), bash và zsh thực hiện hành vi hữu ích hơn khi chỉ VARđược đặt trong khi thực hiện chức năng (nó cũng được xuất). Trong ksh , điều này được thực hiện nếu hàm được xác định bằng cú pháp kshfunction NAME …, không phải nếu nó được xác định với cú pháp tiêu chuẩn NAME (). Trong bash , điều này chỉ được thực hiện ở chế độ bash, không phải ở chế độ POSIX (khi chạy với POSIXLY_CORRECT=1). Trong zsh , điều này được thực hiện nếu posix_builtinstùy chọn không được đặt; tùy chọn này không được đặt theo mặc định mà được bật bởi emulate shhoặc emulate ksh.

Nếu some-commandlà một nội trang, hành vi phụ thuộc vào loại nội dung. Nội dung đặc biệt hoạt động giống như các chức năng. Các phần tử đặc biệt là những phần tử phải được triển khai bên trong lớp vỏ vì chúng ảnh hưởng đến lớp vỏ trạng thái (ví dụ: breakảnh hưởng đến luồng điều khiển, cdảnh hưởng đến thư mục hiện tại, setảnh hưởng đến các tham số vị trí và các tùy chọn Vị trí). Các nội dung khác được tích hợp chỉ để thực hiện và thuận tiện (chủ yếu - ví dụ: tính năng bash printf -vchỉ có thể được thực hiện bởi một nội dung) và chúng hoạt động như một lệnh bên ngoài.

Việc chuyển nhượng diễn ra sau khi mở rộng bí danh, vì vậy nếu some-commandbí danh , trước tiên hãy mở rộng nó để tìm những gì xảy ra.

Lưu ý rằng trong mọi trường hợp, việc gán được thực hiện sau khi dòng lệnh được phân tích cú pháp, bao gồm mọi thay thế biến trên chính dòng lệnh. Vì vậy, var=a; var=b echo $varin a, bởi vì $varđược đánh giá trước khi chuyển nhượng diễn ra. Và do đó IFS=. printf "%s\n" $varsử dụng IFSgiá trị cũ để phân chia $var.

Tôi đã bao gồm tất cả các loại lệnh, nhưng có thêm một trường hợp: khi không có lệnh để thực thi , tức là nếu lệnh chỉ bao gồm các nhiệm vụ (và có thể là chuyển hướng). Trong trường hợp đó, nhiệm vụ vẫn được giữ nguyên . VAR=VALUE OTHERVAR=OTHERVALUEtương đương với VAR=VALUE; OTHERVAR=OTHERVALUE. Vì vậy, sau IFS=. arr=($var), IFSvẫn được đặt thành .. Vì bạn có thể sử dụng $IFStrong chuyển nhượng arrvới kỳ vọng rằng nó đã có giá trị mới, nên có nghĩa là giá trị mới IFSđược sử dụng để mở rộng $var.

Tóm lại, bạn chỉ có thể sử dụng IFSđể tách trường tạm thời :

  • bằng cách bắt đầu một lớp vỏ mới hoặc một lớp con (ví dụ: third=$(IFS=.; set -f; set -- $var; echo "$3")một cách làm phức tạp third=${var#*.*.}ngoại trừ việc chúng hành xử khác nhau khi giá trị varchứa ít hơn hai .ký tự);
  • trong ksh, với IFS=. some-functionvị trí some-functionđược xác định bằng cú pháp ksh function some-function …;
  • trong bash và zsh, IFS=. some-functionmiễn là chúng hoạt động ở chế độ gốc trái ngược với chế độ tương thích.

" IFSvẫn được đặt thành ." Eek. Sau khi đọc phần đầu tiên, điều đó có ý nghĩa, nhưng trước khi tôi đăng Q này, tôi sẽ không mong đợi điều đó.
muru

1
Đây là một câu trả lời cho một câu hỏi khác
schily

Một số giải thích bổ sung trong câu trả lời này từ một số năm trước .
Ti Strga

6

Câu trả lời của @Gilles thực sự tuyệt vời, ông giải thích (chi tiết) một vấn đề phức tạp.

Tuy nhiên, tôi tin rằng câu trả lời cho lý do tại sao lệnh này:

$ IFS=. printf "%s\n" $var
a.b.c

hoạt động như nó là ý tưởng đơn giản rằng toàn bộ dòng lệnh được phân tích cú pháp trước khi nó được thực thi. Và rằng mỗi "từ" được xử lý một lần bởi shell.
Các bài tập, như IFS=., bị trì hoãn (bước 4 là bài cuối cùng):

4.- Mỗi phép gán biến sẽ được mở rộng ...

cho đến trước khi lệnh được thực thi và tất cả các mở rộng trong các đối số được xử lý trước để xây dựng dòng thực thi này:

$ IFS=. printf "%s\n" a.b.c           ## IFS=. goes to the environment.
a.b.c

Giá trị của $varđược mở rộng với IFS "cũ" a.b.ctrước khi lệnh printfđược đưa ra các đối số "%s\n"a.b.c.

Eval

Một mức độ trễ có thể được giới thiệu bởi eval:

$ IFS=. eval printf "'%s\n'" \$var
a
b
c

Dòng được phân tích cú pháp (lần đầu tiên) và 'IFS =.' được đặt thành môi trường như sau:

$ printf '%s\n' $var

Sau đó, nó được phân tích cú pháp một lần nữa:

$ printf '%s\n' a b c

Và thực hiện điều này:

a
b
c

Giá trị của $var(abc) được chia với giá trị của IFS đang sử dụng : ..

Môi trường

Phần phức tạp và khó khăn là những gì có giá trị trong môi trường khi !!!

Điều đó được giải thích rất tốt trong phần đầu tiên của câu trả lời Gilles.

Với một chi tiết bổ sung.

Khi lệnh này được thực thi:

$ IFS=. arr=($var)

Giá trị của IFS được giữ lại trong môi trường hiện tại, có:

$ printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  <.> 

IFS cho một tuyên bố duy nhất.

Nhưng có thể tránh được: Đặt IFS cho một câu lệnh

$ IFS=. command eval arr\=\(\$var\)

$  printf '<%s>  ' "${arr[@]}" "$IFS"
<a>  <b>  <c>  < 
> 

2

Câu hỏi của bạn liên quan

var=a.b.c
IFS=. printf "%s\n" $var

là một trường hợp góc.

Điều này là do macro expansionlệnh in xảy ra trước khi biến shell IFS=.được đặt.

Nói cách khác: khi $varđược mở rộng, IFSgiá trị trước đó được kích hoạt, sau đó IFSđược đặt thành '.'.

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.