Skip to content

Latest commit

 

History

History
1490 lines (1216 loc) · 56.9 KB

Basic.md

File metadata and controls

1490 lines (1216 loc) · 56.9 KB

Básico de código.

  • O que é um shell?
    • Tipos de shell, arquivos de configuração de shell(do sistema e do usuário) - OK
    • Declaração de variaveis e Variaveis de ambiente(env/set/unset/export/alias)
    • Como executar scripts
    • descritores padrão(0,1,2)
    • Comentários, Input, Output e processamento básico, fluxo linear.
    • Escapando linhas longas \
    • Condicionais( if case )
    • Loops(until,while e for) c0d1ngbyt3sfromnone
    • Funções
    • Recebendo valores na execução do script.
    • Carregando outros scripts ( source . )
    • Processamento de dados.

O que é um Shell?

Shell, do inglês significa concha e a razão disso é porquê faz a interface entre o usuário e o sistema operacional, ficando no meio dos dois, como se o Kernel estivesse dentro da concha, observe a imagem abaixo:

Shells

Créditos ao responsável.

Algo engraçado de pensar é que no Ghost in The Shell o shell é aonde o "Ghost" fica, que no caso é o cérebro transplantado, meio que o shell é o que permite a interação com o mundo como um todo, é totalmente épico.

A interface shell funciona como o intermediário entre o sistema operacional e o usuário graças à CLI(command line interface) que é seu novo melhor amigo a partir de agora, você pode não levar a sério mas você vai começar a notar que o shell pode fazer tudo pra você e quanto mais tempo você passar fora dele mais você perde. A função do shell basicamente é ler a linha de comando, interpretar seu significado, executar e devolver o resultado pelas saídas.

Existem vários tipos de shell o que no universo *nix não é novidade, você tem algo livre onde cada um faz a mesma coisa de formas diferentes, isso em alguns casos até chega a ser um problema devido a padronização, contudo existem vários tipos de shell e segue abaixo alguns deles:

Tem umas dessa lista ai que eu nunca ouvi falar e tem vários outros shells que não listei, sinta-se a vontade para clicar nos links. O shell padrão da maioria das distribuições linux é o bash e é o que eu mais uso pois não tenho muito saco pra ficar experimentando outras coisas e é o que vamos usar durante todo esse paper.

Cada usuário no linux quando loga recebe um shell que fica configurado no arquivo /etc/passwd, na verdade nem todos os usuários do sistema recebem um shell por questões de segurança e por lógica não faria sentido atribuir um shell à um usuário do sistema. Quando eu falo usuário do sistema eu me refiro à usuários que o sistema cria para isolar processos, não são usuários para seres humanos. Segue um exemplo do arquivo /etc/passwd

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologinex
proftpd:x:128:65534::/run/proftpd:/bin/false
ftp:x:129:65534::/srv/ftp:/bin/false
mysql:x:130:136:MySQL Server,,,:/nonexistent:/bin/false
docker:x:1001:1001::/home/docker:/bin/bash

A ideia não é explicar a estrutura do arquivo e não o farei, mas você pode ter uma explicação aqui. O que eu quero te mostrar está na ultima coluna delimitada por ':' que é referente à shell do usuário, repare que temos /bin/bash onde /bin é o diretório de binários e shell é o programa que está dentro daquele diretório que é o nosso shell. Repare que tem usuários com /bin/false como o usuário proftpd por exemplo, isso é uma medida de segurança e lógica pois é um usuário do sistema para gerenciar os processos do serviço do daemon(O 'D' no final é referente à daemon, então sempre que você ver algo que termina com 'd' é provável que seja um daemon do sistema.

Como tudo no linux, você pode configurar o shell do seu usário no sistema ou configurar o default do shell no sistema inteiro. Para realizar as configurações no shell do seu usuário você precisa configurar o arquivo ** /home/seuusuario/.bashrc ** e para configurar o shell default de todos os usuário você pode fazer em /etc/bash.bashrc.

Existem dois tipos default de interação no prompt, PS1 e PS2. Suas definições seguem abaixo:

PS1

Basicamente a PS1 é a padrão, é a que nós temos quando entramos na shell, segue:

user@Hope:~$

É uma variável de ambiente configurada no arquivo do usuário que já foi mencionado anteriormente, fica em ** ~/.bashrc**. Abaixo segue o print da variável $PS1;

user@Hope:~$ echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
PS2

A PS2 é quando vamos digitar um comando e quebramos a linha com o caracter '' no final seguido de enter ou quando o comando não termina na linha por que faltou fechar alguma coisa tipo aspas, então caimos na PS2, segue abaixo:

root@OpenStack:/home/operador# echo \
> batata frita
batata frita

Print da variável de ambiente PS2

user@Hope:~$ echo $PS2
>

Outra coisa interessante de se configurar é o $PATH. O $PATH é uma váriável de ambiente assim como PS1 e PS2, porém ela é utilizada para indicar aonde estão os binários do sistema para o usuário. Elas podem ser configuradas em um contexto geral no arquivo /etc/profile ou individualmente no diretório home do usuário ~/.profile.

root@Hope:/home/r3bel# cat ~user/.profile 
# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
	. "$HOME/.bashrc"
    fi
fi

# set PATH so it includes user's private bin directories
PATH="$HOME/bin:$HOME/.local/bin:$PATH"

O PATH é importante pelo fato de que, caso você tenha instalado um binário/programa e ele não fique dentro do diretório padrão, você não vai conseguir invocar esse binário na cli simplesmente pelo nome dele, igual acontece com o comando cat por exemplo.

root@Hope:/home/r3bel# whereis cat
**cat: /bin/cat** /usr/share/man/man1/cat.1.gz

O comando acima indica que o binário cat está no diretório /bin. Para verificar que o diretório /bin está no nosso $PATH, você pode executar o seguinte:

root@Hope:/home/r3bel# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:**/bin**:/usr/games:/usr/local/games

Conforme o output acima, o diretório se encontra no $PATH, logo pode ser invocado pelo nome, sem necessariamente ter que executar o caminho absoluto inteiro. Então você ao invês de fazer isso:

$ /bin/cat  arquivo

Você faz simplesmente isso:

$ cat  arquivo

Para você adicionar algo no $PATH, existem duas formas. A primeira você só precisa redefinir a váriavel e passar o novo local direto na linha de comando, e a outra forma seria alterar o arquivo de configuração citado anteriormente, porém nessa segunda forma, você precisaria relogar no sistema para que as configurações passem a valer.

Primeira forma:

export PATH=$PATH:/seu/diretorio/com/binarios

Segunda forma:

$ vim ~/.profile

E adicione o novo path:

PATH="$HOME/bin:$HOME/.local/bin:$PATH**:/seu/diretorio/com/binarios**"

E então você vai precisar relogar no sistema, pode fazer isso simplesmente executando:

$ su - usuario

Aproveitando que falei sobre caminho absoluto. Existem dois tipos de caminho:

  • Absoluto -> É o caminho completo desde o diretório raiz até o arquivo, lembrando que o diretório raiz é /
  • Relativo -> É o caminho relativo ao diretório atual.

Alguns exemplos para te ajudar a entender do que se trata:

Vamos supor que você está no diretorio ** /etc/ ** para os dois exemplos a seguir. No primeiro exemplo você vai acessar o arquivo de configuração do apache2 pelo caminho relativo:

$ vim apache2/apache2.conf

Agora você vai acessar pelo caminho absoluto:

$ vim /etc/apache2/apache2.conf

A estrutura dos sistemas linux possuem um padrão chamado de FHS, tende a ser uma árvore invertida, observe na imagem abaixo:

Declaração de variaveis e Variaveis de ambiente(env/set/unset/export/alias)

Variáveis são lugares na memória que armazenam coisas, podendo ser sobreescritas se você quiser, mudando o valor que ela contém. Para declarar variáveis no shell é simples:

VARIAVEL=VALOR

VARIAVEL seria o nome da variavel em si, o simbolo de igualdade = significa atribuição, então você está atribuindo VALOR à VARIÀVEIL. Por convenção e padrão, dizem que é melhor declarar todas as variáveis em caixa alta, ou seja maiusculo. Outro ponto curioso que difrente de outras linguagens, no shellscript você não precisa declarar o tipo da variável, o shell entende tudo como string na verdade, mesmo que seja um número, ele vai tratar como string.

Algumas boas práticas em relação à variáveis:

  • Prefira maiúsculas para variáveis globais e minúsculas para variávesi locais e temporárias.
  • Todas variáveis são globais, a não ser quando precedidas pelo comando local.
  • Não são tipadas.
  • Podem ser acessadas da seguinte forma ${nome}

Para acessar a variável que você criou é simples também, basta invocar o comando **echo ** e o nome da variável da seguinte forma:

~$ echo $VARIAVEL
VALOR

Mais um detalhe, o shellscript identifica váriaveis pelo prefixo cifrão $, caso você esqueça, o shell dará um erro dizendo que não encontrou o comando VARIAVEL. Mas não com o echo, pois o echo vai mostrar o que você jogar pra ele. Mas agora vamos ver outro exemplo onde eu possa te mostrar esse erro acontecendo.

Vamos definir uma variável e atribuir à ela a localização de um arquivo e depois invocar o cat passando a variável:

user@Hope:~$ ARQUIVO=/etc/apache2/apache2.conf
user@Hope:~$ cat $ARQUIVO
# This is the main Apache server configuration file.  It contains the
# configuration directives that give the server its instructions.
# See http://httpd.apache.org/docs/2.4/ for detailed information about
# the directives and /usr/share/doc/apache2/README.Debian about Debian specific
# hints.
 . 
 .
 .

Agora caso a gente esqueça de usar o prefixo cifrão na variável, o shell irá retornar um erro:

user@Hope:~$ cat ARQUIVO
cat: ARQUIVO: No such file or directory
ENV

Agora entrando no assunto de enviroment, nós temos várias variáveis definidas no seu ambiente que indicam várias coisas, vocẽ pode visualizar tais variaveis utilizando o comando env:

root@nginx:~# env
LANG=en_US.UTF-8
USER=root
PWD=/root
HOME=/root
MAIL=/var/mail/root
SHELL=/bin/bash
TERM=xterm
SHLVL=1
LOGNAME=root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
_=/usr/bin/env

Outro comando que pode ser adotado é o printenv, a diferença entre os dois comandos é que o printenv pode requisitar os valores de uma opção especifica:

printenv HOME

Vocẽ pode estar utilizando o comando ** env** para definir variaveis de ambiente apenas para executar alguns comandos especificos da seguinte forma:

env VAR1="blahblah" command_to_run command_options
SET/UNSET

O comando set pode ser empregado para os mesmos propositos que os anteriores, se você executar set sem argumentos, ele irá exibir a lista de variaveis de ambiente, variaveis de shell, variaveis local e funções do shelll. É uma lista bem grande então é recomendado utilizar com o comando less

set | less

Nós podemos limpar a saída especificando que o set opere no modo POSIX, modo qual não irá exibir funções do shell. Nós podemos executar isso em um sub-shell evitando que altere variaveis do nosso environment atual:

(set -o posix; set)

O Comando acima vai listar todas as variaveis de ambiente e do shell que foram definidas.

Common Environmental and Shell Variables

Here are some common environmental variables that you will come across:

  • SHELL: This describes the shell that will be interpreting any commands you type in. In most cases, this will be bash by default, but other values can be set if you prefer other options.

  • TERM: This specifies the type of terminal to emulate when running the shell. Different hardware terminals can be emulated for different operating requirements. You usually won't need to worry about this though.

  • USER: The current logged in user.

  • PWD: The current working directory.

  • OLDPWD: The previous working directory. This is kept by the shell in order to switch back to your previous directory by running cd -.

  • LS_COLORS: This defines color codes that are used to optionally add colored output to the ls command. This is used to distinguish different file types and provide more info to the user at a glance.

  • MAIL: The path to the current user's mailbox.

  • PATH: A list of directories that the system will check when looking for commands. When a user types in a command, the system will check directories in this order for the executable.

  • LANG: The current language and localization settings, including character encoding.

  • HOME: The current user's home directory.

  • _: The most recent previously executed command.

An addition to these environmental variables, some shell variables that you'll often see are:

BASHOPTS: The list of options that were used when bash was executed. This can be useful for finding out if the shell environment will operate in the way you want it to.
BASH_VERSION: The version of bash being executed, in human-readable form.
BASH_VERSINFO: The version of bash, in machine-readable output.
COLUMNS: The number of columns wide that are being used to draw output on the screen.
DIRSTACK: The stack of directories that are available with the pushd and popd commands.
HISTFILESIZE: Number of lines of command history stored to a file.
HISTSIZE: Number of lines of command history allowed in memory.
HOSTNAME: The hostname of the computer at this time.
IFS: The internal field separator to separate input on the command line. By default, this is a space.
PS1: The primary command prompt definition. This is used to define what your prompt looks like when you start a shell session. The PS2 is used to declare secondary prompts for when a command spans multiple lines.
SHELLOPTS: Shell options that can be set with the set option.
UID: The UID of the current user.
UNSET

O comando unset pode ser utilizado para remover variaveis e atribudos de variaveis do shell e funções. Por exemplo:

operador@remember:~$ A=B
operador@remember:~$ echo $A
B
operador@remember:~$ unset A
operador@remember:~$ echo $A

operador@remember:~$ set | grep A=B
A=B
operador@remember:~$ unset A 
operador@remember:~$ set | grep A=B
Export

O Comando export pode ser utilizado para exportar variáveis deixando elas disponíveis em todas as threads filhas criadas a partir da sua, o que isso quer dizer, quando vocẽ está numa sessão no shell e vocẽ define uma variável, ela fica restrita apenas áquele environment local, então um exemplo é um script que chama outro scrit. Vamos colocar contexto. Você tem um script que define variaveis e depois chama outro script para executar determinada ação, então vou mostrar um exemplo sem usar export e um utilizando export.

So how do that? Easy ! Script que define a variável e chama outro script para exibir a variavel:

operador@remember:/tmp$ cat testeexport.sh 
#!/bin/bash
VAR=CONTENT
./printvar.sh

Script que irá exibir a variável:

#!/bin/bash
echo $VAR
echo "DONE"

Executando o script:

operador@remember:/tmp$ ./testeexport.sh 
DONE

Repare que nenhum "CONTENT" foi exibido.

Agora vamos utilizar export:

Exporting variable
CONTENT
DONE

Outra forma de utilizar o export é a seguinte:

VARIABLE=CONTENT
export VARIABLE

Então nossa variável fica acessivel em outros processos filhos, facilitando muito o trabalho, vamos supor que você crie uma ferramenta que utilize vários scripts, com o export as váriaveis ficariam disponíveis para todos eles.

Outro exemplo utilizando uma sessão no bash, vamos declarar uma variavel e depois abrir um outro bash dentro do bash e tentar acessar aquela variável.

operador@remember:/tmp$ VAR2='Blaster'
operador@remember:/tmp$ bash
operador@remember:/tmp$ echo $VAR2

operador@remember:/tmp$ exit
exit
operador@remember:/tmp$ export VAR2='Blaster'
operador@remember:/tmp$ bash
operador@remember:/tmp$ echo $VAR2
Blaster

O comando Alias pode ser utilizado para você criar alias mesmo, você pode por exemplo definir um alias que é um comando bem grande e depois apenas invocar com esse 'apelido' do comando.

Se você executar o comando alias apenas ele irá exibir todos os alias definidos:

alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
alias pycharm='/opt/pycharm/bin/pycharm.sh &>/dev/null &'
alias telegram='/home/operador/bin/Telegram/Telegram &>/dev/null &'
alias tor='cd /home/operador/bin/tor-browser_en-US/; ./start-tor-browser.desktop ; cd -'

Vou te mostrar como:

operador@remember:/tmp$ alias listatudo='ls -l /'
operador@remember:/tmp$ listatudo
total 96
drwxr-xr-x   2 root root  4096 Oct 30 15:33 bin
drwxr-xr-x   4 root root  4096 Nov  3 16:00 boot
drwxr-xr-x   2 root root  4096 Jul  8 18:07 cdrom
drwxr-xr-x  23 root root  4360 Nov  3 11:15 dev
drwxr-xr-x 174 root root 12288 Nov  3 16:00 etc
drwxr-xr-x   3 root root  4096 Jul  8 18:08 home
lrwxrwxrwx   1 root root    33 Jul  8 18:14 initrd.img -> boot/initrd.img-4.15.0-20-generic
lrwxrwxrwx   1 root root    33 Jul  8 18:00 initrd.img.old -> boot/initrd.img-4.15.0-20-generic
drwxr-xr-x  24 root root  4096 Jul 22 09:41 lib
drwxr-xr-x   2 root root  4096 Jun 26 12:45 lib64
drwx------   2 root root 16384 Jul  8 18:00 lost+found
drwxr-xr-x   3 root root  4096 Jul  8 18:34 media
drwxrwxrwx  12 root root  4096 Sep  7 18:12 mnt
drwxr-xr-x  21 root root  4096 Oct 30 16:32 opt
dr-xr-xr-x 246 root root     0 Nov  1 19:02 proc
drwx------   9 root root  4096 Aug  8 11:33 root
drwxr-xr-x  40 root root  1220 Nov  3 11:09 run
drwxr-xr-x   2 root root 12288 Nov  3 15:58 sbin
drwxr-xr-x   2 root root  4096 Jun 26 12:44 srv
dr-xr-xr-x  13 root root     0 Nov  1 19:02 sys
drwxrwxrwt  18 root root  4096 Nov  3 16:53 tmp
drwxr-xr-x  10 root root  4096 Jun 26 12:44 usr
drwxr-xr-x  13 root root  4096 Jul 19 19:43 var
lrwxrwxrwx   1 root root    30 Jul  8 18:14 vmlinuz -> boot/vmlinuz-4.15.0-20-generic

Parece meio inutil mas não é, você pode por exemplo colocar as definições no arquivo /.bashrc e então toda vez que o seu usuário logar ele irá carregar esses alias e você poderá executar comandos complexos apenas utilizando o seu respectivo alias, por exemplo, eu criei três alias definidos no arquivo **/.bashrc**:

alias pycharm='/opt/pycharm/bin/pycharm.sh &>/dev/null &'
alias telegram='/home/operador/bin/Telegram/Telegram &>/dev/null &'
alias tor='cd /home/operador/bin/tor-browser_en-US/; ./start-tor-browser.desktop ; cd -'

Então toda vez que eu precisar abrir a IDE pycharm, eu simplesmente digito no shell 'pycharm' e ela é carregada.

Outra coisa interessante sobre os alias é que, se você analisar o arquivo ~/.bashrc vai identificar que existem vários alias ali, por exemplo, pelo menos no meu linux mint:

alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

Isso são alguns atalhos que já vem criados para facilitar sua vida, você poderia por exemplo criar um alias pro comando rm para que ele fique interativo ao invés de já sair apagando, assim você diminui a probabilidade de erro, mas é bem chato ter que ficar confirmando 'Y' para executar o comando porém pode salvar sua vida qualquer dia.

Então vamos lá, vou criar um arquivo chamado delete_me e irei remover ele logo em seguida com nenhum alias do rm criado:

operador@remember:/tmp$ touch delete_me
operador@remember:/tmp$ ls -l delete_me 
-rw-rw-r-- 1 operador operador 0 Nov  3 17:03 delete_me
operador@remember:/tmp$ rm delete_me 
operador@remember:/tmp$ ls -l delete_me 
ls: cannot access 'delete_me': No such file or directory

Agora vamos definir o alias:

operador@remember:/tmp$ alias rm='rm -i'
operador@remember:/tmp$ touch delete_me_again
operador@remember:/tmp$ rm delete_me_again 
rm: remove regular empty file 'delete_me_again'? y
operador@remember:/tmp$ ls delete_me_again
ls: cannot access 'delete_me_again': No such file or directory

Ai se for de seu interesse você pode carregar isso tanto no seu ~/.bashrc ou no /etc/bash.bashrc sendo o segundo arquivo o respnosável pela configuração global, ou seja, todos os usuários iriam carregar esse arquivo e pegar variaveis de ambiente e outras configurações de sessão/shell etc.

Como Executar Scripts

Bom, nesse ponto nós já executamos alguns scripts, mas irei estar explicando como é o processo de forma detalhada.

Scripts de shellscript tem seu sufixo como .sh, então sempre que criar um script utilize isso no final do nome.

Vamos lembrar então das permissões, é algo que a gente chama de UGO, que é um acronimo para User, Group and Others. Todo arquivo tem as propriedades do UGO, as permissões de usuário que tal arquivo pertence, do grupo que o arquivo pertence e permissões para outros.

A nomemclatura é bem simples, você pode utilizar o comando ** ls -l ** para verificar as permissões em arquivos, se liga só:

operador@remember:/tmp$ ls -l script.sh 
-rw-rw-r-- 1 operador operador 30 Nov  3 17:10 script.sh

O que importa aqui é esse pedaço de output aqui:

-rw-rw-r-- 1 operador operador 

O primeiro hífen(-) indica qual o tipo de arquivo, existem vários outros tipos e foge do escopo eu explicar isso agora, mais a frente irei entrar no assunto, contudo o que importa é que quando esse primeiro '-' está apenas como hífen mesmo, significa que é um arquivo normal, regular file. Então nós temos outros 3 blocos com 3 hífens cada. Cada bloco é referente ao UGO, respectivamente, o primeiro bloco ao USER, o segundo ao GROUP e o terceiro á OTHERS.

Existem apenas 3 tipos de permissões, Read(r), Write(w) e Execute(x), logo, no primeiro bloco nós temos rw-, isso significa que só tem permissão de leitura e escrita, no segundo bloco temos rw que também significa leitura e escrita e no ultimo bloco referente ao OTHERS, apenas leitura. Ou seja, não existe nenhuma permissão para execução por parte de nenhuma das três propriedades do UGO.

Se nós tentarmos executar o script, ele irá dar erro:

operador@remember:/tmp$ ./script.sh
bash: ./script.sh: Permission denied

O ./ indica que o arquivo está no diretório local é uma referencia relativa, poderia ser utilizado o caminho absoluto também da seguinte forma:

operador@remember:/tmp$ /tmp/script.sh
bash: /tmp/script.sh: Permission denied

Existe uma forma para conseguir executar arquivos sem necessariamente ter permissão de execução, que seria invocando o interpretador, que no nosso caso é o /bin/bash, mas poderia ser o /bin/sh, ou qualquer outro shell que fosse compativel com o script.

Então segue uma forma de se executar tal script sem permissão de execução explicita:

operador@remember:/tmp$ /bin/bash script.sh 
Lets rOlllll

Existem diversos tipos para se definir permissão para um script, contudo irei abordar o mais simples e apenas referente à permissão de execução, o comando que é responsável para alterar as permissões é o chmod:

operador@remember:/tmp$ chmod u+x script.sh 

A sintaxe é simples, eu utilizei u+x para adicionar permissão de execução apenas ao usuário owner do arquivo. Dẽ uma olhada no manual do ** chmod** para aprender sobre outras formas, existe também a equivalencia em octal, mas não irei entrar nesse assunto.

Uma vez com a permissão definida, você pode verificar as permissões do arquivo novamente:

operador@remember:/tmp$ ls -l script.sh 
-rwxrw-r-- 1 operador operador 30 Nov  3 17:10 script.sh

Se reparar agora, no primeiro bloco existe um x, que é referente à permissão de execução, então é possível executar o script:

operador@remember:/tmp$ ./script.sh 
Lets rOlllll
Descritores padrão.

Bom, falando um pouco sobre descritores de arquivos. Descritore de arquivos são um indicador abstrato que vai fazer o handler sobre como arquivos são acessados ou como entradas e saídas padrão. Não irei entrar a fundo mas irei te dar uma ideia do que estou falando.

Existem três descritores default relacionados à input/output e error, muito utilizados em pipes ou sockets de rede. File Descriptor(FD) são números inteiros não negativos, abaixo segue a relação: Img

  • 0 -> Entrada padrão(STDIN)
  • 1 -> Saída Padrão(STDOUT)
  • 2 -> Saída de erro(STDERR)

Tendo conhecimento disso é possível então redirecionar esses fluxos a seu favor. Um exemplo é a saída padrão, a saída padrão é sempre na console, você pode por exemplo ao invés de exibir na console você poderia encaminhar pra um arquivo, segue exemplo:

operador@remember:/tmp$ ./script.sh 1>output
operador@remember:/tmp$ cat output 
Lets rOlllll

Você faz referência ao número do descritor e encaminha pra algum lugar, no caso ali a referencia foi para o ** 1> ** que é o STDOUT. Uma observação é como os redirecionamentos são feitos, você pode observar que é usado o ** > **, ele vai encaminhar para um arquivo sobreescrevendo, então se nós executarmos novamente o mesmo comando só irá existir uma linha no arquivo. Se for empregado >> ele irá fazer uma adição no final do arquivo, observe:

operador@remember:/tmp$ ./script.sh 1>output
operador@remember:/tmp$ cat output 
Lets rOlllll
operador@remember:/tmp$ ./script.sh 1>output
operador@remember:/tmp$ cat output 
Lets rOlllll
operador@remember:/tmp$ ./script.sh 1>>output
operador@remember:/tmp$ cat output 
Lets rOlllll
Lets rOlllll

Vamos dar uma olhada no STDERR agora, vamos forçar um erro no shell: Com o STDERR normal o erro é exibido na console:

operador@remember:/tmp$ cat void
cat: void: No such file or directory

Um truque é redicionar para o buraco negro do linux conhecido como /dev/null, analogicamente é uma lixeira que não tem possibilidade alguma de restore.

cat void 2>/dev/null

Redirecionando para um arquivo:

operador@remember:/tmp$ cat void 2>error
operador@remember:/tmp$ cat error 
cat: void: No such file or directory

Um outro truque é utilizar o ** &> ** que irá encaminhar tanto STDOUT quanto STRERR para o mesmo local. No exemplo abaixo eu irei utilizar o comando cat para exibir o conteúdo de dois arquivos, o primeiro existe e o segundo não:

operador@remember:/tmp$ cat script.sh blablalba
#!/bin/bash
echo Lets rOlllll
cat: blablalba: No such file or directory

Agora redirecionando com o &> eu encamido o STDOUT e o STRERR para um arquivo por exemplo:

operador@remember:/tmp$ cat script.sh blablalba &>output
operador@remember:/tmp$ cat output 
#!/bin/bash
echo Lets rOlllll
cat: blablalba: No such file or directory

Existe também o comando ** tee ** que trabalha com esse esquema de redirecionamento porém ele exibe na tela ao mesmo tempo que redireciona para algum lugar, vale a pena dar uma olhada no manual desse cara.

Agora falando sobre pipes, pipes são aquelas barras ** '|' ** e utilizando pipes voCê pode exemplo redirecionar o STDOUT de um comando para o STDIN de outro comando:

cat script.sh | wc -l
2

No exemplo acima é redirecionado o STDOUT do cat para wc -l que irá contar as linhas do arquivo.

operador@remember:/tmp$ cat script.sh | /bin/bash 
Lets rOlllll

Algo que é bastante utilizado é pegar um arquivo de log e usar o grep para filtrar determinadas expressões:

tail -f /var/log/apache2/access.log | grep 134.23.44.56

Iremos ir mais a fundo sobre esse assunto mais além.

- Input, Output e processamento básico, fluxo linear.

Ta na hora de colocar a mão na massa!

A primeira coisa na hora de se criar um script é definir o shebang, esse cara é responsável por indicar aonde está o interpretador que irá interpetar o script, nós vamos estar utilizando o bash e ele está localizado no /bin/bash, então nosso shebang fica assim:

#!/bin/bash

Sim, esse #! é o que indica o shebang e o que vem depois é o path pro intereptador.

Um comentário no código é uma linha que o interpretador ignora, você pode usar isso para escrever comentários no código que expliquem o que está ocorrendo, facilitando o entedimento do código ou até mesmo para comentar linhas de código enquanto realiza algum debug, para definir um comentário, apenas adicione #, sim, hashtag, tralha, jogo da velha, qual nome você preferir.

# Um comentário válido
echo "Blabla" # Um comentário válido depois de uma instrução echo, a instrução echo é executada normalmente porém tudo depois do # não será.
# echo "Blabla" Essa linha inteira não será interpretada. 

Então vamos começar com o básico, sempre falo que um programa é divido em três partes, entrada, processamento e saída, vocẽ tem a entrada de dados, processa e depois retorna o resultado. Vamos começar de trás pra frente, começar em como ter output de dados do programa:

Para exibir conteúdo na tela é utilizado o comando echo.

Aqui tem um programa bobo que mostra ** Hello Friend** de trẽs formas diferentes:

#!/bin/bash
echo 'Hello Friend'
echo "Hello Friend"
echo Hello Friend

Antes de executar não esqueça de dar permissão.

Executando o código:

operador@remember:/tmp$ ./script.sh 
Hello Friend
Hello Friend
Hello Friend

Ele exibiu os três 'echo's na tela, a diferença entre as três formas é a seguinte:

#!/bin/bash
NOME='Fulano'
echo 'Hello Friend $NOME'
echo "Hello Friend $NOME"
echo Hello Friend $NOME

Executando:

operador@remember:/tmp$ ./script.sh 
Hello Friend $NOME
Hello Friend Fulano
Hello Friend Fulano

Quando se utiliza aspas simples, toda instrução dentro não é executada, ele entende como apenas texto. Quando se utiliza aspas duplas, ele tenta interpretar nomes de variáveis. Assim como sem aspas.

Operadores Matematicos: Exitem duas formas de se realizar calculos, tem até uma terceira mas é gambiarra mas vou estar falando, talvez seja util para você algum dia.

A primeira é utilizando $(( DO THE MATH HERE)), esse é o meu favorito porquẽ considero mais intuitivo, segue exemplo:

#!/bin/bash
A=10
B=5
echo $((A+B))
C=$((A+B))
echo $C

echo $(( A/B ))
echo $(( A*B ))
echo $(( A-B ))

Uma segunda forma é utilizar o ** expr **:

#!/bin/bash
A=10
B=5
expr $A + $B
expr $A - $B
expr $A / $B
expr $A \* $B
A=$(expr $A \* $B)
echo $A

Repare que na multiplicação foi necessário escapar com uma contra barra para que o * não fosse interpretado de outra forma.

Um detalhe que não comentei é que para executar o resultado de uma outra instrução como nos exemplos anteriores em uma variável, é necessário chamar um subshell que irá executar aquilo e então será atribuido à variável:

A=$(expr $A \* $B)
C=$((A+B))

Então aqui nosso código segue um fluxo linear de acordo com as instruções do seu script até chegar ao final.

Como receber valores de entrada de forma interativa? Simples, usamos o read para tal.

#!/bin/bash
echo 'Informe um número: '
read A
echo 'Informe outro número: '
read B
echo $A $B 

Uma coisa importante é um leve detalhe, se você não passar aonde o read vai armazenar o valor, ele armazena numa variavel default chamda REPLY

#!/bin/bash
echo 'Informe um número: '
read
echo $REPLY

Condicionais( if case )

Para se ter um fluxograma no programa com tomada de decisões é empregado as condicionais, no shellsript nos temos duas funções que processam condições:

  • if
  • case
IF

O IF pode ser utilizado para verificar se uma ou mais condições são verdadeiras, se forem verdadeiras, um bloco de código é executado, se não, outro bloco é executado, assim nós temos desvio de fluxo dentro do programa o que é bem interessante.

Então vamos começar do básico: Primeiro vamos definir o que é o test command.

$ test "a" == "x" && echo ok || echo not
not
$ test "a" == "a" && echo ok || echo not
ok
$ test 3 -eq 3 && echo ok || echo not ok 
ok
[ "awesome" = "awesome" ]; echo $?
0
  • 0 -> true
  • 1 -> False
$ [ "awesome" = "aweso2e" ]; echo $?
1

Agora vamos falar sobre o que isso tem a ver com o IF.

Você pode notar que o test é basicamente a verificação de uma condição, muita gente usa o IF e não sabe o que é o test, mas são duas coisas semelhantes porém diferentes. O IF vai verificar se aquele test retorna true, se sim, ele executa um bloco, se não ele faz outra coisa, podendo ser uma nova verificação.

A sintaxe do if é bem simples, você define o IF e então quais os tests que você irá realizar, então coloca o que será executado, existe duas formas básicas de se utilizar:

if [ TEST SOMETHING ];then
	DO SOMETHING
fi 

E a outra forma é colocando o then na linha de baixo, que seria o correto, no exemplo anterior foi usado o ';' para separar um do outro, mas isso é o que vocẽ preferir, não é que é errado usar o exemplo anterior, você pode usar o que te agradar mais.

if [ TEST SOMETHING ]
then
	DO SOMETHING
fi 

Assim como a maioria das outras linguagens nós temos também o else que indica, se o bloco do if não for executado porquê ele considerou a condição como FALSE ele irá executar outra coisa, segue exemplo:

if [ TEST SOMETHING ]
then
	DO SOMETHING
else 
	DO SOMETHING ELSE 
fi 

Então com base nisso nós já podemos por exemplo fazer uma calculadora com os conhecimentos adquiridos até aqui, eu não passei lógica de programação mas espero que você já saiba, se você já sabe, pare de ler até a proxima linha, caso não saiba, pode continuar e tenten entender o código.

Para realizar as operações de test nas condicionais, é necessário que você adote alguns verificações entre as variaveis, segue abaixo uma tabela com os mais usados:

Comparando Strings
  • -z -> Verdadeiro se a string estiver vazia.
  • -n -> Verdadeiro se a string não estiver varia, operação default.
  • = -> Verdadeoro se as Strings forem iguais.
  • != -> Verdadeiro se as Strings forem diferentes.

No shellscript, toda variável é tratada como string, então é bom você verificar colocando aspas duplas para indicar que o parâmetro a ser comparado seja uma string.

Aritmeticos:
  • -eq -> Verdadeiro se os valores forem iguais.
  • -ne -> Verdadeiro se os valores forem diferentes.
  • -le -> Verdadeiro se o primeiro parametro for menor ou igual ao segundo
  • -ge -> Verdadeiro se o primeiro parametro for maior ou igual ao segundo
  • -lt -> Verdadeiro se o primeiro parametro for menor que o segundo.
  • -gt -> Verdadeiro se o primeiro parametro for maior que o segundo.
Make Your Own Code: Escreve um script que receba dois numeros e um operador matemático básico e então identifique qual operação será executada e processe a operação e depois exiba o resultado.
Solução:
#!/bin/bash
echo 'Informe um número: '
read NUM_A
echo 'Informe outro numero: '
read NUM_B
echo 'Operação:[+;-;x;/] '
read OPT

if [ $OPT = "+" ];then
	RESULT=$(( NUM_A + NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
fi
if [ $OPT = "-" ];then
	RESULT=$(( NUM_A - NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
fi
if [ $OPT = 'x' ];then
	RESULT=$(( NUM_A * NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
fi
if [ $OPT = '/' ];then
	RESULT=$(( NUM_A / NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
fi

Existe uma forma melhor do que ir criando vários IF's sequenciais, um dos motivos para se evitar isso é que você não precisa por exemplo verificar todos as condicionais se você já encontrou qual a operação, então isso meio que gasta mais processamento atoa, para ter um melhor controle disso você pode adotar uma cadeia de condições, quando uma é alcançada as que ainda não foram verificadas são desconsideradas. Muita gente usa isso colocando um IF dentro de um IF, porém o shellscript possui o elif, o elif basicamente diz que, se o if deu False então faça uma nova verificação, definida naquele elif. Um exemplo de sintaxe segue:

if [ IS TRUE? ];then
	DO SOMETHING 
elif [ IS TRUE ? ];then
	DO SOMETHING 
elif [ IS TRUE ? ];then
	DO SOMETHING 

elif [ IS TRUE ? ];then
	DO SOMETHING 
else
	DO SOMETHING ELSE
fi

Isso pode ter ficado confuso mas você irá entender com o código abaixo:

#!/bin/bash
echo 'Informe um número: '
read NUM_A
echo 'Informe outro numero: '
read NUM_B
echo 'Operação:[+;-;x;/] '
read OPT

if [ $OPT = "+" ];then
	RESULT=$(( NUM_A + NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
elif [ $OPT = "-" ];then
	RESULT=$(( NUM_A - NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
elif [ $OPT = 'x' ];then
	RESULT=$(( NUM_A * NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
elif [ $OPT = '/' ];then
	RESULT=$(( NUM_A / NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
else
	echo "[!!] Operação $OPT desconhecida"
fi

Uma outra coisa interessante que eu considero gambiarra é, no exemplo que não foi adotado o elif, você poderia ter utilizado o ** exit** para encerrar o programa em cada condição, logo, a condição que fosse válida iria ter seu bloco de código executado e o fluxo do programa encerrava naquele bloco e as condicionais restantes não iriam ser verificadas:

Então dessa forma você iria verificar apenas até a condição que fosse verdadeira e as demais não seriam executadas.

#!/bin/bash
echo 'Informe um número: '
read NUM_A
echo 'Informe outro numero: '
read NUM_B
echo 'Operação:[+;-;x;/] '
read OPT

if [ $OPT = "+" ];then
	RESULT=$(( NUM_A + NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	exit 0
fi
if [ $OPT = "-" ];then
	RESULT=$(( NUM_A - NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	exit 0
fi
if [ $OPT = 'x' ];then
	RESULT=$(( NUM_A * NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	exit 0
fi
if [ $OPT = '/' ];then
	RESULT=$(( NUM_A / NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	exit 0
fi

Note que caso você queria executar algum outro código depois do bloco inteiro do IF e ele tiver entrado em alguma condicional, ele jamais chegará ao fim do programa devido ao exit. Nesses casos é melhor você utilizar o elif ou o case e esse problema de tentar sair dentro de uma condição não irá ocorrer pois isso já é tratado pela natureza das funções.

Bom, nós temos também os operadores lógicos, segue abaixo:

  • && -> operador lógico AND.
  • || -> operador lógico OR.
  • ! -> Operador lógico XOR, negação.

Os operadores lógicos são muito utilizados em condicionais para verificar uma ou mais condições. Vamos à sintaxe:

#!/bin/bash
if [  COND1 ] && [ COND2  ];
then
	Vai executar apenas se COND1 e COND2 forem verdade, se um dos dois não for, não irá executar esse bloco.

elif ! [ COND3 ] ;then
	Vai executar apenas se COND3 retornar falso, então o operador lógico XOR irá inverter para True. Caso COND3 seja True, o XOR irá converter para False e não será executado. 

elif [ COND5 ] || [ COND6 ] ;then
 	Será executado ou se COND5 for verdadeiro ou COND6, apenas uma das duas condições precisa dar verdadeiro. Só não será executado se nenhuma for verdadeiro. Se as duas forem verdadeiros também será True. 
fi

Existe uma tabela chamada ** Tabela Verdade**, essa tabela mapeia as possibilidades combinatórias e o resultado sendo calculado por um operador lógico. Tabela Verdade AND:

Tabela Verdade OU

Tabela Verdade XOR:

Bom, vamos colocar a mão na massa, segue um exercicio de fixação: Escreva um programa que recebe o nome e idade do usuário e então verifique se o nome foi inserido e se ele é maior de 18, caso for, exiba que ele pode servidr o exericto, se ele não for, verifique se o nome é válido e se a é suficiente.

#!/bin/bash
echo 'Digite seu nome: '
read NAME
echo 'Digite sua Idade: '
read AGE
echo -n "$NAME" | wc -c
if [ $(echo -n "$NAME"| wc -c ) -gt 1 ] && [ $AGE -ge 18 ];
then
	echo '[=] Você pode servir o exercito.'


elif [ $( echo -n "$NAME" | wc -c  ) -gt 1  ] && [ $AGE -lt 18 ] ;then
	echo "[!] Desculpe $NAME, você não possui idade suficiente"
elif [ $( echo -n "$NAME" | wc -c  ) -eq 0  ] || [ $AGE -lt 18 ] ;then
	echo '[!] Nome Invalido ou Idade insuficiente.'
fi

O Comando wc será o responsável por verificar quantos caracteres existem no conteudo da variável $NAME.

Uma outra forma de utilizar o if é a seguinte:

if test "$VAR" = 'something'
then
	do something
fi

Outra forma de redirecionar fluxo do programa é atráves do comando ** switch**. Esse comando é bastante utilizado Menus interativos ou onde se existe diversa possibilidaedes, ele verifica a segunda opção independente do resultado da primeiram então é importante adotar os exit**

Exemplo de sintaxe:

case $OPT in
 '+')
        Executa se o conteudo de $OPT for '='
	;;

   "-")
        Executa se o conteudo de $OPT for '-'
	;;

  'x')
        Executa se o conteudo de $OPT for 'x'

	;;
 '/')
        Executa se o conteudo de $OPT for '/'

	;;
  *) 
        Executa se o conteudo de $OPT não for nenhum dos anteriores, é o default error quando não encontra nenhum match. 
	;;
esac

Repare que o case fecha o bloco com um esac e toda verificação termina o bloco com ;;.

Então vamos ao handsON. Escreva um programa que receba dois valores e solicite o tipo de operação básica matematica e então utilizando o case processe a operação e exiba o resultado.

#!/bin/bash
echo 'Informe um número: '
read NUM_A
echo 'Informe outro numero: '
read NUM_B
echo 'Operação:[+;-;x;/] '
read OPT

case $OPT in
   '+')
        RESULT=$(( NUM_A + NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	;;

   "-")
        RESULT=$(( NUM_A - NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	;;

  'x')
	RESULT=$(( NUM_A * NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	;;

 '/')	RESULT=$(( NUM_A / NUM_B ))
	echo "[-] $NUM_A $OPT $NUM_B = $RESULT"
	;;

  *) 
        Executa se o conteudo de $OPT não tiver sido localizado, é a saída caso nenhuma condição ocorra antes. 

	;;
esac
LOOPS, for, while e until.

Vamos falar de laços ou loops, pode chamar de como quiser, ambos são válidos mas eu prefiro chamar de loop. Loops são blocos de códigos que são executados várias vezes até uma condição se torne verdadeira ou deixe de ser verdadeira. No shellscript nós temos três tipos de loop:

  • for -> Executa uma determinado número de vezes.
  • while -> Executa até que determinada condição deixe de ser verdadeira.
  • until -> Executa até que determinada condição se torne falsa.

Então vamos começar com o loop do tipo for A Sintaxe do comando é bem simples, segue:

for aux in SOMETHING;do
	Do Something  with $aux
done

A variável aux irá receber cada interação do que quer que seja o SOMETHING e pode ter o nome que você quiser. Repare que aqui nós temos a delimitação do bloco utilizando do e done

Segue um exemplo de loop do for percorrendo um valor número de 0 até 10 e exibe os números pares:

#!/bin/bash
for a in {0..10}
do
	if [ $(( a%2 )) -eq 0 ];then
	        echo $a
	fi
done

No exemplo anterior é utilizado a operação % que é responsável por calcular o resto da divisão.

Você poderia por exemplo utilizar o controle com base em arquivos, segue exemplo:

#!/bin/bash
echo '[+] Digite o path de um arquivo de texto para ser lido'
read
for i in $(cat $REPLY);do
       echo $i | wc -c 
done                                                                  

O exemplo acima irá ler todas as linhas do path que irá fazer referência à um arquivo e então irá contar quantos caracteres tem cada linha. Esse tal arquivo poderia ser o /etc/passwd, uma lista de bruteforce ou qualquer outra coisa que se possa ler e tenha linhas.

operador@remember:/tmp$ grep 20 auth_port ; echo $?
1
operador@remember:/tmp$ grep 22 auth_port ; echo $?
22
0
grep 22 auth_port &>/dev/null ; echo $?
0

Mais um exemplo utilizando o loop For para identificar as portas autorizadas por uma lista de portas que podem estar em listen. As portas que não estiverem no arquivo não estão autorizada.

#!/bin/bash
read -p 'Arquivo com portas autorizadas: '
echo '[+] Verificando portas que estão em LISTEN'
for i in $(netstat -tln4W | awk '{print $4}' | cut -d ':' -f 2  | tail -n +3);do
	if [ $( grep $i $REPLY &>/dev/null ; echo $? ) -eq 1 ];then
		echo "Porta $i não está autorizada"			
	else 
		echo "Porta $i está autorizada"
	fi	
done

Então nós estamos usando o comando netstat para exibir as portas ativas e passando para a STDIN do comando awk , usando o cut** para delimitar por ':' e pegar o segundo campo e então com o ** tail nós pegamos todo a saída formatada removendo apenas as 3 primeiras linhas. Esse comando irá gerar uma lista das portas que estão em listen para que nosso for possa percorrer.

Uma vez dentro do loop For nós temos uma bloco de condição IF, onde é utilizado o ** grep** para procurar dentro do arquivo que o usuário vai definir e pegar o resultado da execução do comando anterior que irá indicar se ele encontrou ou não aquela porta na lista. Se o resultado do comando der 1, significa que a posta não está autorizada.

Para você entender melhor:

Lista com as portas autorizadas:
cat auth_port 
22
53
110

Verificando o resultado da execução do comamdo grep

operador@remember:/tmp$ grep 20 auth_port ; echo $?
1
operador@remember:/tmp$ grep 22 auth_port ; echo $?
22
0
grep 22 auth_port &>/dev/null ; echo $?
0

Todas as saídas que retornarem 1 é porquê não existem no arquivo, as que retornarem 0 existem. Uma observação é que quando o comando grep executa com sucesso e localiza a string que vocẽ está buscando ele exibe na tela a string. Para contornar isso nós redirecionamos o STDOUT e o STDERR para /dev/null do comando grep assim só o resultado da execução será exibido com o comando $?

Esse ultimo exemplo é mais para tentar te mostrar o poder do shell e se você for criativo você já poderia por exemplo criar uma regra no firewall pra bloquear aquela porta ou matar o PID do socket, encaminhar um e-mail pra você mesmo ou qualquer outra coisa, a criatividade é o limite.

Loop While O loop while é outra coisa simples, ele vai executar até que uma condição deixe de ser verdadeira, falando ao contrário, ele vai executar até que uma condição se torne falsa.

Sintaxe:

while [ condition ]
do
	command1
	command2
	commandN
done

Um exemplo simples de implementação do While:

#!/bin/bash
read -p 'Num Users:'
COUNT=0 
echo $COUNT $REPLY
while [ $COUNT -lt $REPLY ]
do
	echo "[-] Loop $COUNT"
	COUNT=$(( COUNT+1 ))
done	

Só ressaltando que a condição não necessariamente precisaria ser uma aritmetica, poderia qualquer verificação que retornasse true ou false, por exemplo esse script que verifica se o servidor apache2 parou de executar e se a condição for verdade ele executa automaticamente o daemon:

#!/bin/bash
set -x 
if   [ $( id -u ) -ne 0 ]  &&  [ $(id -u) -ne $( id -u www-data ) ] ;then
	echo '[!!] Esse programa precisa ter usuário privilegiado para execução'
	exit 1
fi 
while [ 1 -eq 1 ];do
	if [ $( pidof apache2  &>/dev/null ; echo $?) -eq 1 ];then
		echo "[-] Apache2 is Dead"
		echo '[-] Restarting '
		sudo systemctl status apache2
		sudo systemctl start apache2
		echo "$?"
		sudo systemctl  status apache2
		sudo systemctl start apache2
		echo "$?"
	fi
	sleep 2
done	

Foi empregado o comando ** id ** para verificar se o usuário estava executando era um usuário administrativo ou se era um usuário que responsável pelo daemon. Então temos o loop While Repare que o loop é um loop infinito que espera por 2 segundos antes de cada execução devido ao comando sleep.

** Until: ** O Until funciona de uma forma diferente, ele irá executar até que a condição se torne verdadeira: Sintaxe:

until [ condition ]
do
   command1
   command2
   ...
   ....
   commandN
done

Um exemplo simples:

#!/bin/bash
i=1
until [ $i -gt 6 ]
do
	echo "Hello $i."
	i=$(( i+1 ))
done

FUNÇÔES:

Comando local: -> Use sempre o comando local para proteger todas as variáveis de uma função.

Exemplo:

#!/bin/bash
function bla(){
	local bla
	bla=10
	echo $bla
}
bla='peixe'
echo "Função teste: $bla"
bla
echo "Função teste: $bla"
operador@remember:/tmp$ /bin/bash testelocal.sh 
Função teste: peixe
10
Função teste: peixe
#!/bin/bash
function bla(){
	bla=10
	echo $bla
}
bla='peixe'
echo "Função teste: $bla"
bla
echo "Função teste: $bla"
Função teste: peixe
10
Função teste: 10
  • Sempre colocar a s funções no início do programa, logo após as variáveis globais.
  • Funções só podem retornar números de ) a 255, usando o comando return
  • Funções podem retornar strings usando o comando echo.
  • Funções podem gravar variáveis globais mas evite fazer isso.
  • Funções não são nada além de pequenas subrotinas ou subscripts. É interessante adotar funções para modularizar o código tornando mais fácil a manutenção.
  • Funções de shell não retornam valor mas sim um status_code.

Existem duas formas de se declarar uma função:

function name(){
  Commands
}

E a outra forma é:

name(){
 Commands
 return $TRUE
}

#!/bin/bash function hello_func(){ echo "Hello $1" }

function hello_input(){ echo $INPUT }

function parssing( $TESTE){ echo "holly $TESTE" } echo "[+] Starting Routine" hello_func $1 export INPUT=$2 hello_input

Argumentos -------

Vamos falar agora sobre argumentos. Argumentos são implementados em muitos programas seja para pedir help ou alterar a forma de operação, por exemplo:

ls --help

O help foi foi um argumento passado ao programa ls

Agora vamos a um exemplo que melhor irá nos exibir isso:

#!/bin/bash
echo Numero de Argumentos $#
echo Argumento 0 $0
echo Argumento 1 $1
echo Argumento 2 $2

Ao executar esse programa ele irá exibir o número de argumentos e os argumentos em suas devidas posições:

./opts.sh primeiro segundo terceiro 
Numero de Argumentos 3
Argumento 0 ./opts.sh
Argumento 1 primeiro
Argumento 2 segundo

Então a partir disso é possível receber argumentos na execução do script podendo ser utilizado para deixar o programa não interativo, parametrizado como alguns dizem. Repare que ** $#** exibe quantos argumentos foram dados. Isso poderia ser utilizado para chamar uma função de usage por exemplo:

#!/bin/bash
function usage(){
	echo "Para utilizar esse programa digite o arquivo qual será lido, segue exemplo:"
	echo "$0 /etc/passwd"
}
if [ $# -lt 1 ];then
	usage
	exit 1 
else 
	cat $1 
fi

Note que o $0 sempre exibe o path de execução, exatamente como o comando foi executado, para remover aquele PATH da exibição no programa, no nosso caso ./, você pode utilizar o comando basename da seguinte forma:

	echo "$(basename $0) /etc/passwd"

Uma outra forma que eu acredito ser melhor é usar o getopts:

#!/bin/bash
function usage(){
	echo "Esse programa requer argumentos para executar:"
	echo "-f -> Arquivo que será lido"
	echo "-o -> Arquivo que será criado com o conteúdo do arquivo lido"
	echo "$0 -f /etc/passwd -o /tmp/output"
	exit 1 
}

if [ $# -lt 1 ];then
	usage	
else
	while getopts 'f:o:h' opt;do
		case $opt in 
			f) FILE=$OPTARG
				;;
			o) OUTPUTFILE=$OPTARG
				;;
			h) usage
				;;

			*) usage ;;
		esac
	done
	cat $FILE >> $OUTPUTFILE
fi

Bom, no exemplo acima é importante notar um detalhe, é o seguinte:

while getopts 'f:o:h' opt;do

As opções ** 'f:o:h' ** indicam qual o argumento que será passado, onde cada flag(f,o,h) que tem como sequencia o caracter ':' indica que será atribuido valor, as que não possuem ':' como sequencia não vão ser atribuidos valores, logo a $OPTARG não é atribuida.

O mesmo procedimento poderia ter sido utilizado com o while, case e o comando shitf, o resultado seria o mesmo. A diferença de usar a forma anterior citata nessa frase e utilizar o getopts é que o getopts não suporta argumentos longos(--argumento-longo), apenas curtos.

Carregando outros scripts (source)

Vamos supor que você que executar vários scripts de dentro do seu programa, existem várias formas de fazer isso: Se referindo ao caminho absoluto do programa:

#!/bin/bash
echo "[+] Iniciando Rotina, carregando primeiro programa:"
/tmp/primeiro.sh

Ou se referindo ao caminho relativo do programa, porém nesse caso ambos vão ter que estar no mesmo diretório ou o programa que será inicializado terá que estar em algum subdiretório, claro que poderia ser feito aquela gambiarra de ir voltnado nos diretórios usando o comando cd mas acho isso feio.

#!/bin/bash
echo "[+] Iniciando Rotina, carregando primeiro programa:"
./primeiro.sh

Mas agora vamos supor que você tenha um programa que usa um arquivo de texto para se configurar com algumas variáveis. Exemplo do arquivo com as variáveis:

VAR=blabla
VAR1=bla1bla1

Exemplo do programa:

#!/bin/bash
echo "[+] Iniciando Rotina, carregando configurações:"
source file.conf
echo "[!!] Configurações carregadas"
echo $VAR 
echo $VAR1

Agora executando o programa:

./load.sh 
[+] Iniciando Rotina, carregando configurações:
[!!] Configurações carregadas
blabla
bla1bla1

Processamento de Dados

Antes de entrarmos de fato na elaboração de programas para pentest, vamos só dar um overview rápido em processamento de texto com os próprios programas no linux.

O primeiro programa que iremos dar uma olhada é o grep, este programa é utilizado para procurar strings em texto, a sintaxe delf é bem simples:

grep string arquivo

Por exemplo:

grep 127.0.0.1 /var/log/apache2/access.log.1
127.0.0.1 - - [14/Nov/2018:22:16:20 -0200] "GET /shell.php?cmd=ls HTTP/1.1" 404 424 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0"
127.0.0.1 - - [14/Nov/2018:22:16:20 -0200] "GET /favicon.ico HTTP/1.1" 404 425 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0"
127.0.0.1 - - [14/Nov/2018:22:17:02 -0200] "GET /shell.php?cmd=ls HTTP/1.1" 200 242 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0"
127.0.0.1 - - [14/Nov/2018:22:17:02 -0200] "GET /favicon.ico HTTP/1.1" 404 425 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0"

O grep é um comando bem parrudo e possui variantes como o fgrep e o egrep, podendo buscar até por expressões regulares, recomendo muito que você dê uma olhada nas man pages.

O comando cut faz um trabalho bem bacana cortando textos, exemplo de sintaxe:

cut -d 'delimitador' -f 'num do campo que está sendo delimitado' 

Vamos pegar o mesmo arquivo anterior e pegar apenas os endereços IP's que acessar o servidor apache2:

cut -d '-' -f 1 /var/log/apache2/access.log.1
127.0.0.1 
127.0.0.1 
127.0.0.1 
127.0.0.1 

Bom, a primeira parte desse paper fica por aqui, espero que tenham tirado algum proveito, os demais papers vão focar explicitamente no desenvolvimento de ferramentas para ajudar você no seu pentest.

"Don't comment bad code, rewrite it."

Fontes:

https://www.computerhope.com/unix/test.htm http://br.ccm.net/contents/320-linux-o-shell https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps https://en.wikipedia.org/wiki/File_descriptor http://wiki.bash-hackers.org/commands/classictest