Como o Linux lida com scripts de shell?

22

Para esta pergunta, vamos considerar um script shell bash, embora essa pergunta deva ser aplicável a todos os tipos de script shell.

Quando alguém executa um shell script, o Linux carrega todo o script de uma só vez (talvez na memória) ou lê comandos de script um por um (linha por linha)?

Em outras palavras, se eu executar um script de shell e excluí-lo antes que a execução seja concluída, a execução será encerrada ou continuará como está?

Usuário Registrado
fonte
3
Tente. (Ele vai continuar.)
devnull
1
@ devnull Na verdade, há uma pergunta interessante aqui. Concedido, se continuará ou não, é trivial testar, mas há diferenças entre arquivos binários (carregados na memória) e scripts com uma linha shebang ou scripts sem uma linha shebang.
terdon
1
Você pode estar interessado nesta resposta
terdon
23
Para o propósito de sua intenção real, de excluir o script de shell durante sua execução, não importa se ele é lido de uma vez ou linha por linha. No Unix, um inode não é realmente excluído (mesmo se não houver links para ele em nenhum diretório) até que o último arquivo aberto seja fechado. Em outras palavras, mesmo que seu shell leia o script de shell linha por linha durante a execução, ainda é seguro excluí-lo. A única exceção é se o seu shell for do tipo que fecha e reabre o script do shell a cada vez, mas se isso acontecer, você terá problemas muito maiores (de segurança).
Chris Jester-Young

Respostas:

33

Se você usar, stracepoderá ver como um script de shell é executado quando é executado.

Exemplo

Digamos que eu tenho esse script de shell.

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

Executando usando strace:

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

Uma olhada no strace.logarquivo revela o seguinte.

...
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)     
...

Depois que o arquivo é lido, ele é executado:

...
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)                           = ?

No exposto acima, podemos ver claramente que o script inteiro parece estar sendo lido como uma única entidade e depois executado depois. Portanto, "apareceria" pelo menos no caso de Bash que ele lê o arquivo e o executa. Então, você acha que pode editar o script enquanto está em execução?

NOTA: Não! Leia para entender por que você não deve mexer com um arquivo de script em execução.

E quanto a outros intérpretes?

Mas sua pergunta está um pouco errada. Não é o Linux que está necessariamente carregando o conteúdo do arquivo, é o intérprete que está carregando o conteúdo, então depende muito de como o intérprete é implementado, se ele carrega o arquivo inteiramente ou em blocos ou linhas por vez.

Então, por que não podemos editar o arquivo?

Se você usar um script muito maior, notará que o teste acima é um pouco enganador. De fato, a maioria dos intérpretes carrega seus arquivos em blocos. Isso é bastante padrão com muitas das ferramentas do Unix, onde eles carregam blocos de um arquivo, processam e depois carregam outro bloco. Você pode ver esse comportamento com as perguntas e respostas de perguntas e respostas que escrevi há um tempo atrás grep, intituladas: Quanto texto o grep / egrep consome a cada vez? .

Exemplo

Digamos que criamos o seguinte script de shell.

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

Resultando neste arquivo:

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

Que contém o seguinte tipo de conteúdo:

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

Agora, quando você executa isso usando a mesma técnica acima, com 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

Você notará que o arquivo está sendo lido em incrementos de 8 KB, portanto, o Bash e outros shells provavelmente não carregarão um arquivo por inteiro, em vez disso, eles serão lidos em blocos.

Referências

slm
fonte
@terdon - sim, eu lembro de ter visto essas perguntas e respostas antes.
slm
5
Com um script de 40 bytes, com certeza, é lido em um bloco. Tente com um script> 8kB.
Gilles 'SO- stop be evil'
Eu nunca tentei, mas acho que a remoção de arquivos não é realmente feita até que todos os processos fechem o descritor de arquivo associado ao arquivo removido, portanto, o bash pode continuar lendo o arquivo removido.
Farid Nouri Neshat
@ Gilles - sim, eu adicionei um exemplo, estava chegando a ele.
slm
2
Esse comportamento depende da versão. Eu testei com o bash versão 3.2.51 (1) -release e descobri que ele não ultrapassava a linha atual (consulte esta resposta do stackoverflow ).
Gordon Davisson
11

Isso depende mais do shell do que do SO.

Dependendo da versão, kshleia o script sob demanda por um bloco de 8k ou 64k bytes.

bashleia o script linha por linha. No entanto, como as linhas de fato podem ter um comprimento arbitrário, ele lê cada 8176 bytes a partir do início da próxima linha para analisar.

Isto é para construções simples, isto é, um conjunto de comandos simples.

Se comandos estruturados do shell forem usados ​​( um caso que a resposta aceita não considere ) como um for/do/doneloop, uma case/esacopção, um documento aqui, um subshell entre parênteses, uma definição de função etc. e qualquer combinação dos itens acima, os interpretadores do shell lêem até o final da construção para garantir primeiro que não haja erro de sintaxe.

Isso é um tanto ineficiente, pois o mesmo código pode ser lido várias vezes, mas atenuado pelo fato de que esse conteúdo é normalmente armazenado em cache.

Qualquer que seja o interpretador do shell, é muito imprudente modificar um script do shell enquanto ele está sendo executado, pois o shell é livre para ler novamente qualquer parte do script e isso pode levar a erros inesperados de sintaxe, se estiver fora de sincronia.

Observe também que o bash pode falhar com uma violação de segmentação quando não é possível armazenar uma construção de script muito grande que o ksh93 pode ler perfeitamente.

jlliagre
fonte
7

Isso depende de como o intérprete executando o script funciona. Tudo o que o kernel faz é notar que o arquivo a ser executado começa com#! , essencialmente executa o restante da linha como um programa e fornece o executável como argumento. Se o intérprete listado lá lê esse arquivo linha por linha (como shells interativos fazem com o que você digita), é isso que você obtém (mas as estruturas de loop de várias linhas são lidas e mantidas para repetir); se o intérprete coloca o arquivo na memória, processa-o (talvez o compile em uma representação intermediária, como Perl e Pyton), o arquivo é lido na íntegra antes de executar.

Se você excluir o arquivo enquanto isso, ele não será excluído até que o intérprete o feche (como sempre, os arquivos desaparecem quando a última referência, seja uma entrada de diretório ou um processo que o mantém aberto) desaparece.

vonbrand
fonte
4

O arquivo '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

A corrida:

~/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, um arquivo não é excluído desde que o processo o mantenha aberto. A exclusão apenas remove o DIRENT fornecido.


fonte