eval
và exec
cả hai đều được xây dựng trong các lệnh của bash (1) để thực thi các lệnh.
Tôi cũng thấy exec
có một vài lựa chọn nhưng đó có phải là sự khác biệt duy nhất? Điều gì xảy ra với bối cảnh của họ?
eval
và exec
cả hai đều được xây dựng trong các lệnh của bash (1) để thực thi các lệnh.
Tôi cũng thấy exec
có một vài lựa chọn nhưng đó có phải là sự khác biệt duy nhất? Điều gì xảy ra với bối cảnh của họ?
Câu trả lời:
eval
và exec
là những con thú hoàn toàn khác nhau. (Ngoài thực tế là cả hai sẽ chạy các lệnh, nhưng mọi thứ bạn làm trong một vỏ cũng vậy.)
$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
Replace the shell with the given command.
Những gì exec cmd
không, hoàn toàn giống như chỉ chạy cmd
, ngoại trừ lớp vỏ hiện tại được thay thế bằng lệnh, thay vì một quá trình riêng biệt đang được chạy. Trong nội bộ, chạy nói /bin/ls
sẽ gọi fork()
để tạo ra một quy trình con, và sau đó exec()
trong đứa trẻ để thực thi /bin/ls
. exec /bin/ls
mặt khác sẽ không ngã ba, mà chỉ thay thế vỏ.
So sánh:
$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo
với
$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217
echo $$
in ra PID của shell mà tôi đã khởi động và việc liệt kê /proc/self
cung cấp cho chúng tôi PID của ls
shell được chạy từ shell. Thông thường, ID tiến trình là khác nhau, nhưng có exec
vỏ và ls
có cùng ID tiến trình. Ngoài ra, lệnh sau exec
không chạy, vì shell đã được thay thế.
Mặt khác:
$ help eval
eval: eval [arg ...]
Execute arguments as a shell command.
eval
sẽ chạy các đối số như một lệnh trong shell hiện tại. Nói cách khác eval foo bar
là giống như chỉ foo bar
. Nhưng các biến sẽ được mở rộng trước khi thực hiện, vì vậy chúng ta có thể thực thi các lệnh được lưu trong các biến shell:
$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo
Nó sẽ không tạo ra một tiến trình con, vì vậy biến được đặt trong trình bao hiện tại. (Tất nhiên eval /bin/ls
sẽ tạo ra một quy trình con, giống như cách mà một người già đơn giản /bin/ls
sẽ làm.)
Hoặc chúng ta có thể có một lệnh xuất ra các lệnh shell. Chạy ssh-agent
bắt đầu tác nhân trong nền và xuất ra một loạt các phép gán biến, có thể được đặt trong trình bao hiện tại và được sử dụng bởi các tiến trình con (các ssh
lệnh bạn sẽ chạy). Do đó ssh-agent
có thể được bắt đầu với:
eval $(ssh-agent)
Và shell hiện tại sẽ lấy các biến cho các lệnh khác để kế thừa.
Tất nhiên, nếu biến số cmd
có chứa thứ gì đó như thế rm -rf $HOME
, thì chạy eval "$cmd"
sẽ không phải là thứ bạn muốn làm. Ngay cả những thứ như thay thế lệnh bên trong chuỗi sẽ được xử lý, vì vậy người ta phải thực sự chắc chắn rằng đầu vào eval
là an toàn trước khi sử dụng nó.
Thông thường, có thể tránh eval
và tránh thậm chí vô tình trộn mã và dữ liệu sai cách.
eval
ở nơi đầu tiên cho câu trả lời này quá. Những thứ như biến đổi gián tiếp có thể được thực hiện trong nhiều shell thông qua declare
/ typeset
/ nameref
và mở rộng như thế ${!var}
, vì vậy tôi sẽ sử dụng chúng thay vì eval
trừ khi tôi thực sự phải tránh nó.
exec
không tạo ra một quy trình mới. Nó thay thế quy trình hiện tại bằng lệnh mới. Nếu bạn đã làm điều này trên dòng lệnh thì nó sẽ kết thúc phiên shell của bạn một cách hiệu quả (và có thể đăng xuất bạn hoặc đóng cửa sổ terminal!)
ví dụ
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
Tôi đang ở đây ksh
(vỏ bình thường của tôi). Tôi bắt đầu bash
và sau đó bên trong bash tôi exec /bin/echo
. Chúng ta có thể thấy rằng tôi đã bị rơi trở lại ksh
sau đó vì bash
quá trình này được thay thế bằng /bin/echo
.
exec
được sử dụng để thay thế quy trình shell hiện tại bằng bộ mô tả chuyển hướng / tệp mới và xử lý luồng nếu không có lệnh nào được chỉ định. eval
được sử dụng để đánh giá các chuỗi như các lệnh. Cả hai có thể được sử dụng để xây dựng và thực thi một lệnh với các đối số được biết đến trong thời gian chạy, nhưng exec
thay thế quá trình của trình bao hiện tại bên cạnh việc thực thi các lệnh.
Cú pháp:
exec [-cl] [-a name] [command [arguments]]
Theo hướng dẫn nếu có lệnh được chỉ định tích hợp sẵn này
... Thay thế vỏ. Không có quá trình mới được tạo ra. Các đối số trở thành đối số để chỉ huy.
Nói cách khác, nếu bạn đang chạy bash
với PID 1234 và nếu bạn chạy exec top -u root
trong lớp vỏ đó, thì top
lệnh đó sẽ có PID 1234 và thay thế quy trình shell của bạn.
Cái này hữu ích ở đâu? Trong một cái gì đó được gọi là kịch bản lệnh bao bọc. Các tập lệnh như vậy xây dựng các tập hợp đối số hoặc đưa ra quyết định nhất định về biến nào sẽ truyền vào môi trường và sau đó sử dụng exec
để thay thế chính nó bằng bất kỳ lệnh nào được chỉ định và tất nhiên cung cấp các đối số tương tự mà tập lệnh bao bọc đã tạo ra trên đường đi.
Những gì hướng dẫn cũng nêu là:
Nếu lệnh không được chỉ định, mọi chuyển hướng đều có hiệu lực trong trình bao hiện tại
Điều này cho phép chúng tôi chuyển hướng bất cứ thứ gì từ luồng đầu ra của shell hiện tại vào một tệp. Điều này có thể hữu ích cho mục đích ghi nhật ký hoặc lọc, nơi bạn không muốn thấy stdout
các lệnh mà chỉ stderr
. Chẳng hạn, như vậy:
bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt
2017年 05月 20日 星期六 05:01:51 MDT
HELLO WORLD
Hành vi này giúp thuận tiện cho việc đăng nhập các tập lệnh shell , chuyển hướng các luồng đến các tệp hoặc quy trình riêng biệt và các nội dung thú vị khác với các mô tả tệp.
Trên cấp mã nguồn ít nhất là cho bash
phiên bản 4.3, tích exec
hợp được xác định trong builtins/exec.def
. Nó phân tích cú pháp các lệnh đã nhận và nếu có bất kỳ lệnh nào, nó sẽ chuyển mọi thứ vào shell_execve()
hàm được định nghĩa trong execute_cmd.c
tệp.
Tóm lại, tồn tại một nhóm exec
lệnh trong ngôn ngữ lập trình C và shell_execve()
về cơ bản là một hàm bao của execve
:
/* Call execve (), handling interpreting shell scripts, and handling
exec failures. */
int
shell_execve (command, args, env)
char *command;
char **args, **env;
{
Các trạng thái thủ công bash 4.3 (nhấn mạnh thêm bởi tôi):
Các đối số được đọc và nối với nhau thành một lệnh duy nhất. Lệnh này sau đó được đọc và thực thi bởi shell và trạng thái thoát của nó được trả về là giá trị của eval.
Lưu ý rằng không có quá trình thay thế xảy ra. Không giống như exec
mục tiêu là mô phỏng execve()
chức năng, tích eval
hợp chỉ phục vụ để "đánh giá" các đối số, giống như khi người dùng đã gõ chúng trên dòng lệnh. Như vậy, các quy trình mới được tạo ra.
Trường hợp này có thể hữu ích? Như Gilles đã chỉ ra trong câu trả lời này , "... eval không được sử dụng thường xuyên. Trong một số shell, cách sử dụng phổ biến nhất là lấy giá trị của một biến không biết tên cho đến khi chạy". Cá nhân, tôi đã sử dụng nó trong một vài tập lệnh trên Ubuntu khi cần thực thi / đánh giá một lệnh dựa trên không gian làm việc cụ thể mà người dùng hiện đang sử dụng.
Ở cấp độ mã nguồn, nó được xác định trong builtins/eval.def
và chuyển chuỗi đầu vào được phân tích cú pháp sang evalstring()
hàm.
Trong số những thứ khác, eval
có thể gán các biến vẫn còn trong môi trường thực thi shell hiện tại, trong khi exec
không thể:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
tạo một tiến trình con mới, chạy các đối số và trả về trạng thái thoát.
Uh cái gì Toàn bộ vấn đề eval
là nó không theo bất kỳ cách nào tạo ra một quy trình con. Nếu tôi làm
eval "cd /tmp"
trong một shell, sau đó shell hiện tại sẽ có thư mục thay đổi. Không exec
tạo ra một tiến trình con mới, thay vào đó, nó thay đổi tệp thực thi hiện tại (cụ thể là shell) cho cái đã cho; id quá trình (và các tệp đang mở và các thứ khác) giữ nguyên. Trái ngược với eval
, một exec
sẽ không trở lại vỏ gọi trừ khi exec
chính nó bị lỗi do không thể tìm hoặc tải tệp thực thi hoặc chết để tranh luận về các vấn đề mở rộng.
eval
về cơ bản diễn giải (các) đối số của nó dưới dạng một chuỗi sau khi ghép, cụ thể là nó sẽ thực hiện thêm một lớp mở rộng ký tự đại diện và phân tách đối số. exec
không làm bất cứ điều gì như thế.
Đánh giá
Những công việc này:
$ echo hi
hi
$ eval "echo hi"
hi
$ exec echo hi
hi
Tuy nhiên, những điều này không:
$ exec "echo hi"
bash: exec: echo hi: not found
$ "echo hi"
bash: echo hi: command not found
Quá trình thay thế hình ảnh
Ví dụ này cho thấy cách exec
thay thế hình ảnh của quá trình gọi của nó:
# Get PID of current shell
sh$ echo $$
1234
# Enter a subshell with PID 5678
sh$ sh
# Check PID of subshell
sh-subshell$ echo $$
5678
# Run exec
sh-subshell$ exec echo $$
5678
# We are back in our original shell!
sh$ echo $$
1234
Lưu ý rằng exec echo $$
chạy với PID của subshell! Hơn nữa, sau khi nó hoàn thành, chúng tôi đã trở lại trong sh$
vỏ ban đầu của chúng tôi .
Mặt khác, eval
không phải thay thế hình ảnh quá trình. Thay vào đó, nó chạy lệnh đã cho như bình thường trong vỏ. (Tất nhiên, nếu bạn chạy một lệnh yêu cầu một quá trình được sinh ra ... nó sẽ làm điều đó!)
sh$ echo $$
1234
sh$ sh
sh-subshell$ echo $$
5678
sh-subshell$ eval echo $$
5678
# We are still in the subshell!
sh-subshell$ echo $$
5678
exec
:)