Como o Linux lida com scripts de shell?

21

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

Quando alguém executa um script de shell, o Linux carrega todo o script de uma só vez (na memória talvez) ou lê os 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á finalizada ou continuará como está?

    
por Registered User 23.03.2014 / 14:17

4 respostas

33

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

Exemplo

Digamos que eu tenha este script de shell.

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

Executando-o usando strace :

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

Observar o arquivo strace.log 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)     
...

Quando o arquivo for lido, ele será 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)                           = ?

Acima, podemos ver claramente que o script inteiro parece estar sendo lido como uma entidade única e depois executado depois. Então, seria "aparecer" pelo menos no caso do Bash, que lê o arquivo e o executa. Então você acha que poderia editar o roteiro enquanto ele está rodando?

NOTA: Não, no entanto! Continue lendo para entender por que você não deve mexer em um arquivo de script em execução.

E os outros intérpretes?

Mas sua pergunta está um pouco errada. Não é o Linux que está necessariamente carregando o conteúdo do arquivo, é o interpretador que está carregando o conteúdo, então é até mesmo como o interpretador implementou se ele carrega o arquivo inteiramente ou em blocos ou linhas de cada vez.

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

Se você usar um script muito maior, você notará que o teste acima é um pouco enganador. Na verdade, a maioria dos intérpretes carrega seus arquivos em blocos. Isso é bastante comum em muitas das ferramentas do Unix, onde eles carregam blocos de um arquivo, o processam e carregam outro bloco. Você pode ver esse comportamento com as Perguntas e respostas sobre o assunto que escrevi há algum tempo sobre grep , intitulado: Quanto texto grep / egrep consome cada vez? .

Exemplo

Digamos que façamos 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

Como resultado 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ê executar 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 8KB, então o Bash e outros shells provavelmente não carregarão um arquivo inteiro, ao invés de lê-los em blocos.

Referências

por 23.03.2014 / 16:50
11

Isso é mais dependente do shell que o sistema operacional.

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

bash leu o script linha por linha. No entanto, dadas as linhas de fato podem ser de comprimento arbitrário, ele lê cada vez 8176 bytes a partir do início da próxima linha para analisar.

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

Se os comandos estruturados do shell forem usados ( um caso, a resposta aceita não é considerada ) como um for/do/done loop, um case/esac switch, um here, um subshell entre parênteses, um definição de função, etc. e qualquer combinação dos acima, os interpretadores de shell lêem até o final da construção para primeiro se certificar de que não há erro de sintaxe.

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

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

Note também que o bash pode travar com uma violação de segmentação quando não puder armazenar uma construção de script excessivamente grande que o ksh93 possa ler na perfeição.

    
por 23.03.2014 / 18:04
7

Isso depende de como o interpretador executando o script funciona. Tudo o que o kernel faz é notar que o arquivo para executar inicia com #! , essencialmente executa o resto da linha como um programa e fornece o executável como argumento. Se o interpretador listado lá ler o arquivo linha por linha (como os 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 repetição); se o interpretador devora o arquivo na memória, o processa (talvez compila para uma representação intermediária, como Perl e Pyton) o arquivo é lido por completo antes de ser executado.

Se você excluir o arquivo enquanto isso, o arquivo 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 mantendo-a aberta) desaparece. / p>     

por 23.03.2014 / 16:49
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 um processo o mantenha aberto. A exclusão apenas remove o dado DIRENT.

    
por 23.03.2014 / 17:19