对于这个问题,让我们考虑一个bash shell脚本,尽管这个问题必须适用于所有类型的shell脚本。
当某人执行Shell脚本时,Linux是一次将所有脚本加载(可能是加载到内存中)还是一次(一行一行地)读取脚本命令?
换句话说,如果我执行一个shell脚本并在执行完成之前将其删除,该执行将终止还是继续进行?
对于这个问题,让我们考虑一个bash shell脚本,尽管这个问题必须适用于所有类型的shell脚本。
当某人执行Shell脚本时,Linux是一次将所有脚本加载(可能是加载到内存中)还是一次(一行一行地)读取脚本命令?
换句话说,如果我执行一个shell脚本并在执行完成之前将其删除,该执行将终止还是继续进行?
Answers:
如果您使用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可能不会完全加载文件,而是以块的形式读取文件。
这与外壳程序有关,而不与操作系统有关。
根据版本,ksh
按8k或64k字节块的要求按需读取脚本。
bash
逐行读取脚本。但是,鉴于事实行可以是任意长度,因此它每次从下一行的开头读取8176个字节来进行解析。
这是用于简单的构造,即一组简单的命令。
如果使用shell结构化的命令(这种情况下,缺少考虑的可接受答案),例如for/do/done
循环,case/esac
开关,here文档,用括号括起来的子shell,函数定义等,以及上述内容的任意组合,则shell解释程序会读取到构建结束时,首先要确保没有语法错误。
由于可以一次又一次地读取相同的代码多次,但是由于通常会缓存此内容,因此这在某种程度上是低效的。
不管使用哪种解释器,在执行脚本时修改一个脚本都是非常不明智的,因为该脚本可以自由地再次读取脚本的任何部分,如果不同步,可能会导致语法错误。
还要注意,当bash无法存储过大的脚本构造时,bash可能会因分段违规而崩溃,ksh93可以完美读取。
“ 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。