Bash đang tự động tải lại (tiêm) các cập nhật vào tập lệnh đang chạy khi lưu nó: Tại sao? Bất kỳ sử dụng thực tế?


10

Tôi đã viết một tập lệnh bash và tình cờ cập nhật mã (đã lưu tệp tập lệnh vào đĩa) trong khi tập lệnh đang chờ một số đầu vào trong một whilevòng lặp. Sau khi tôi trở lại thiết bị đầu cuối và tiếp tục với lời gọi trước đó của tập lệnh, bash đã báo lỗi về cú pháp tệp:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

Vì vậy, tôi đã cố gắng làm như sau:

Thứ 1: tạo tập lệnh, self-update.shhãy gọi nó là:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

Những gì kịch bản làm là đọc mã của nó, thay đổi từ 'TRƯỚC' thành 'SAU', sau đó tự viết lại bằng mã mới.

Chạy thứ 2 :

chmod +x self-update.sh
./self-update.sh

Kỳ quan thứ 3 ...

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

Bây giờ, tôi đã không đoán được rằng trong cùng một lời mời, nó sẽ xuất ra SAU! , trên lần chạy thứ hai chắc chắn, nhưng không phải trên lần đầu tiên.

Vì vậy, câu hỏi của tôi là: nó có chủ ý (theo thiết kế)? hoặc là do cách bash chạy tập lệnh? Từng dòng hoặc lệnh bằng lệnh. Có bất kỳ sử dụng tốt của hành vi như vậy? Bất kỳ ví dụ về nó?


Chỉnh sửa: Tôi đã thử định dạng lại tệp để đặt tất cả lệnh vào một dòng, hiện tại nó không hoạt động:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

Đầu ra:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

Trong khi khi di chuyển echochuỗi sang dòng tiếp theo, hãy tách chuỗi đó khỏi cplệnh gọi viết lại ( ):

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

Và bây giờ nó hoạt động trở lại:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.


1
Tôi đã xem qua nguồn, nhưng tôi thực sự không thể tìm thấy bất kỳ lời giải thích nào cho việc này. Tôi khá chắc chắn rằng tôi đã thấy một số trình cài đặt tự giải nén được triển khai như các kịch bản shell vài năm trước. Các tập lệnh này chứa một số dòng lệnh shell thực thi và sau đó là một khối dữ liệu khổng lồ được đọc bởi các lệnh này. Vì vậy, tôi sẽ cho rằng điều này được thiết kế theo cách này để bash không phải đọc toàn bộ tập lệnh vào bộ nhớ, nó sẽ không hoạt động với các tập lệnh tự giải nén lớn.
Martin von Wittich

Dưới đây là ví dụ về tập lệnh SFX: ftp.games.skynet.be/pub/wolfenstein/ mẹo
Martin von Wittich

Đẹp! Tôi nhớ đã thấy các tập lệnh tự cài đặt này, trình điều khiển AMD Catalyst (độc quyền) vẫn xuất xưởng như thế này, nó tự giải nén và sau đó cài đặt. Chúng phụ thuộc vào hành vi như vậy của việc đọc các tập tin trong khối. Cảm ơn ví dụ!
aularon

Câu trả lời:


12

Đây là do thiết kế. Bash đọc các tập lệnh trong khối. Vì vậy, nó sẽ đọc một phần của tập lệnh, chạy bất kỳ dòng nào có thể, và sau đó đọc đoạn tiếp theo.

Vì vậy, bạn gặp phải một cái gì đó như thế này:

  • Bash đọc 256 byte đầu tiên (byte 0-255) của tập lệnh.
  • Trong 256 byte đầu tiên đó là một lệnh mất một lúc để chạy và bash bắt đầu lệnh đó, chờ cho nó thoát.
  • Trong khi lệnh đang chạy, tập lệnh được cập nhật và phần được thay đổi là sau 256 byte mà nó đã đọc.
  • Khi lệnh bash đang chạy kết thúc, nó tiếp tục đọc tệp, tiếp tục từ vị trí của nó, nhận byte 256-511.
  • Phần kịch bản đó đã thay đổi, nhưng bash không biết điều đó.

Trường hợp điều này càng trở nên rắc rối hơn là nếu bạn chỉnh sửa bất cứ điều gì trước byte 256. Hãy nói rằng bạn xóa một vài dòng. Sau đó, dữ liệu trong tập lệnh ở byte 256, bây giờ ở một nơi khác, giả sử ở byte 156 (trước đó là 100 byte). Bởi vì điều này, khi bash tiếp tục đọc, nó sẽ nhận được những gì ban đầu là 356.

Đây chỉ là một ví dụ. Bash không nhất thiết phải đọc 256 byte mỗi lần. Tôi không biết chính xác nó đọc được bao nhiêu tại một thời điểm, nhưng điều đó không quan trọng, hành vi vẫn như vậy.


Không, nó đọc theo từng đoạn, nhưng tua lại đến nơi để chắc chắn đọc lệnh tiếp theo như sau khi lệnh trước đó trở lại.
Stéphane Chazelas

@StephaneChazelas Bạn lấy từ đâu vậy? Tôi vừa mới thực hiện một bước tiến và thậm chí không có nhiều stattập tin để xem nếu nó thay đổi. Không có lseekcuộc gọi.
Patrick

Xem pastie.org/8662761 để biết các phần có liên quan của đầu ra strace. Xem cách echo foothay đổi thành một echo bartrong sleep. Nó đã hoạt động như vậy cho đến tận phiên bản 2, vì vậy tôi không nghĩ đó là vấn đề về phiên bản.
Stéphane Chazelas

Rõ ràng nó đọc từng dòng tệp, tôi đã thử với tệp và đó là những gì tôi tìm ra. Tôi sẽ chỉnh sửa câu hỏi của tôi để làm nổi bật hành vi đó.
aularon

@Patrick nếu bạn có thể cập nhật câu trả lời của mình để phản ánh nó đang đọc từng dòng, vì vậy tôi có thể chấp nhận câu trả lời của bạn. (Kiểm tra chỉnh sửa của tôi cho câu hỏi về vấn đề đó).
aularon
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.