Linux如何处理Shell脚本?


22

对于这个问题,让我们考虑一个bash shell脚本,尽管这个问题必须适用于所有类型的shell脚本。

当某人执行Shell脚本时,Linux是一次将所有脚本加载(可能是加载到内存中)还是一次(一行一行地)读取脚本命令

换句话说,如果我执行一个shell脚本并在执行完成之前将其删除,该执行将终止还是继续进行?


3
试试吧。(它将继续。)
devnull 2014年

1
@devnull实际上这里有一个有趣的问题。当然,是否继续进行测试很简单,但是二进制文件(已加载到内存中)与带有shebang行的脚本或不带有shebang行的脚本之间存在差异。
terdon

1
您可能对此答案
terdon

23
为了您的实际意图,在执行过程中删除shell脚本,是一次读取还是逐行读取都无所谓。在Unix中,直到关闭最后一个打开的文件,实际上不会删除它(即使没有任何目录的链接)。换句话说,即使您的shell在执行过程中逐行读取了shell脚本,仍然可以安全地删除它。唯一的例外是,如果您的外壳是每次都会关闭并重新打开外壳脚本的那种外壳,但是如果这样做,您将遇到更大的(安全)问题。
克里斯·杰斯特·杨

Answers:


33

如果您使用strace它,可以看到运行时如何执行shell脚本。

说我有这个shell脚本。

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

使用运行它strace

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

看一下strace.log文件内部,将发现以下内容。

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

读完文件后,便会执行:

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

在上面,我们可以清楚地看到整个脚本似乎是作为单个实体读取的,然后在此之后执行。因此,至少在Bash的情况下,它将“出现”在读取文件中,然后执行该文件。因此,您认为可以在脚本运行时对其进行编辑吗?

注意:但是不要!继续阅读以了解为什么不应该弄乱正在运行的脚本文件。

那其他口译员呢?

但是您的问题略有偏离。不是Linux一定要加载文件的内容,而是解释器要加载的内容,因此,是由解释器实现的方式完全取决于文件是完全加载还是一次加载块或行。

那为什么我们不能编辑文件呢?

但是,如果使用更大的脚本,则会注意到上述测试有点误导。实际上,大多数解释器将其文件装入块中。这是许多Unix工具的标准,它们在其中加载文件的块,进行处理然后再加载另一个块。您可以通过我之前写的有关U&L问答的行为看到这种行为grep,标题为:grep / egrep每次消耗多少文本?

假设我们制作以下shell脚本。

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

产生此文件:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

其中包含以下类型的内容:

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

现在,当您使用与上面相同的技术来运行它时strace

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

您会注意到文件正在以8KB的增量读取,因此Bash和其他Shell可能不会完全加载文件,而是以块的形式读取文件。

参考文献


@terdon-是的,我记得以前曾经进行过问答。
slm

5
当然,使用40字节的脚本,它可以在一个块中读取。尝试使用> 8kB脚本。
吉尔(Gilles)'所以

我从未尝试过,但是我认为直到所有进程关闭与已删除文件关联的文件描述符后,删除文件才真正完成,因此bash可能会继续从已删除文件中读取。
Farid Nouri Neshat 2014年

@Gilles-是的,我添加了一个例子。
slm

2
此行为与版本有关。我使用bash版本3.2.51(1)-release进行了测试,发现它没有缓冲超过当前行(请参见stackoverflow答案)。
Gordon Davisson 2014年

11

这与外壳程序有关,而不与操作系统有关。

根据版本,ksh按8k或64k字节块的要求按需读取脚本。

bash逐行读取脚本。但是,鉴于事实行可以是任意长度,因此它每次从下一行的开头读取8176个字节来进行解析。

这是用于简单的构造,即一组简单的命令。

如果使用shell结构化的命令(这种情况下,缺少考虑的可接受答案),例如for/do/done循环,case/esac开关,here文档,用括号括起来的子shell,函数定义等,以及上述内容的任意组合,则shell解释程序会读取到构建结束时,首先要确保没有语法错误。

由于可以一次又一次地读取相同的代码多次,但是由于通常会缓存此内容,因此这在某种程度上是低效的。

不管使用哪种解释器,在执行脚本时修改一个脚本都是非常不明智的,因为该脚本可以自由地再次读取脚本的任何部分,如果不同步,可能会导致语法错误。

还要注意,当bash无法存储过大的脚本构造时,bash可能会因分段违规而崩溃,ksh93可以完美读取。


7

这取决于运行脚本的解释器的工作方式。内核所做的只是注意到要执行的文件从开始#!,本质上将其余的行作为程序运行,并给其可执行文件作为参数。如果那里列出的解释器逐行读取该文件(就像交互式外壳程序一样,您键入内容),那么您将得到(但是多行循环结构被读取并保留以供重复);如果解释器将文件插入到内存中,然后对其进行处理(也许将其编译为中间表示形式,如Perl和Pyton一样),则在执行之前已完全读取文件。

如果同时删除文件,则在解释器将其关闭之前,文件不会被删除(与往常一样,当最后一个引用(无论是目录条目还是保持打开状态的进程)消失时,文件都会消失)。


4

“ x”文件:

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

运行:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRC文件不会被删除,只要进程保持打开状态即可。删除只会删除给定的DIRENT。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.