# Shell UNIX

Caminhos, expansão de nomes, redirecionamentos, pipes, processos e consulta do manual.

Página: https://resumos.rgo.pt/cadeiras/so/shell-unix/

A linha `sort -n < numeros.txt | uniq > distintos.txt` envolve três tarefas diferentes: a shell abre os ficheiros e liga os canais, `sort` ordena os números e `uniq` elimina repetições adjacentes. A **shell** interpreta comandos e argumentos e prepara a entrada e a saída dos programas. Para prever o resultado, acompanha primeiro essa preparação e depois os bytes que cada programa recebe.

## Caminhos e diretório de trabalho

Um caminho absoluto começa em `/`, a raiz. Um relativo começa no diretório de trabalho. `.` designa esse diretório e `..` o seu pai. `~` expande para a pasta pessoal quando usado numa posição apropriada da linha de comando.

```
mkdir -p treino/a/b
cd treino/a/b
pwd
cd ../..
pwd
```

Depois do primeiro `cd` estás em `treino/a/b`; `../..` sobe dois níveis até `treino`. O resultado absoluto de `pwd` depende da pasta onde começaste. `cd` sem argumento leva-te à pasta pessoal.

## Expandir nomes e proteger texto

Supõe uma pasta com `main.c`, `ajuda.c` e `notas.txt`. A shell expande `*.c` para os dois nomes existentes que terminam em `.c`, antes de correr o comando. `?` corresponde a um carácter e `[ab]` a um carácter do conjunto. As aspas protegem o texto contra certas expansões.

```
printf '%s\n' *.c
find . -name '*.c' -print
```

No primeiro comando, a shell procura os nomes na pasta corrente, ou seja a expansão acontece antes de `printf` correr. No segundo, as aspas entregam `*.c` ao `find`, que procura na árvore. Sem aspas, o padrão pode ser expandido cedo e dar argumentos diferentes dos pretendidos.

Aspas simples conservam o texto literal. Aspas duplas conservam espaços, mas ainda permitem expansões como `$HOME`. A expansão `{a,b}` da Bash produz duas alternativas de texto, mesmo que os ficheiros não existam. Um glob não é uma expressão regular: o `*` de `grep` tem outro significado.

## Três canais e redirecionamento

Um processo costuma começar com os descritores 0, 1 e 2 abertos, respetivamente entrada padrão, saída padrão e erros padrão. Na biblioteca C, as interfaces correspondentes são `stdin`, `stdout` e `stderr`, do tipo `FILE *`.

| Operador da shell | Efeito |
| --- | --- |
| `< entrada.txt` | Liga a entrada padrão ao ficheiro |
| `> saida.txt` | Cria ou trunca o ficheiro e liga a saída padrão |
| `>> saida.txt` | Acrescenta a saída no fim |
| `2> erros.txt` | Redireciona os erros padrão |
| `2>&1` | Faz o descritor 2 referir o destino atual do 1 |
| `a | b` | Liga a saída padrão de `a` à entrada padrão de `b` |

```
printf '9\n2\n9\n5\n' > numeros.txt
sort -n < numeros.txt | uniq > distintos.txt
cat distintos.txt
```

| Etapa | Bytes que seguem para a etapa seguinte |
| --- | --- |
| Entrada de `sort` | `9\n2\n9\n5\n` |
| Saída de `sort -n` | `2\n5\n9\n9\n` |
| Saída de `uniq` | `2\n5\n9\n` |

A saída final é `2`, `5`, `9`, uma por linha. `sort -n` ordena numericamente e `uniq` elimina repetições adjacentes, por isso sem ordenar duas ocorrências separadas poderiam sobreviver.

A shell retira `< numeros.txt` da lista de argumentos de `sort`, por isso o programa recebe dados por stdin, não o texto `<`. Este detalhe explica por que um programa com `argc == 1` ainda consegue ler um ficheiro redirecionado.

A ordem dos redirecionamentos importa. `comando > tudo.txt 2>&1` envia os dois canais para o ficheiro. Em `comando 2>&1 > saida.txt`, stderr conserva o destino que stdout tinha antes de mudar.

Não uses `sort ficheiro > ficheiro`: a shell pode truncar o destino antes de `sort` o ler. Usa um destino diferente e, depois de confirmar o resultado, substitui o original.

## Filtrar e contar

```
grep -v '^#' dados.txt | cut -d ':' -f 1 | sort
sed 's/erro/aviso/g' < registo.txt
head -n 3 dados.txt
tail -n 3 dados.txt
```

`grep -v` exclui linhas que correspondem ao padrão. `cut` escolhe campos usando `:` como separador. `sed` substitui todas as ocorrências por linha por causa de `g`. Estes comandos não alteram o ficheiro de entrada por si só.

`wc -l` conta caracteres de mudança de linha, não linhas visuais, ou seja `printf abc | wc -l` dá 0. `wc -c` conta bytes, por isso um carácter acentuado em UTF-8 pode ocupar mais de um byte.

## Processos e jobs

`comando &` inicia um job sem a shell esperar pelo seu fim antes de apresentar outro prompt. O processo não fica automaticamente independente do terminal nem imune ao logout, porque continuas ligado à mesma sessão.

`jobs` mostra os jobs da shell e `ps` consulta os processos do sistema; `ps -A | wc -l` é apenas uma aproximação ao seu número, porque pode incluir um cabeçalho, os próprios programas da pipeline e alterações ocorridas enquanto os dados são recolhidos.

`Ctrl+C` costuma enviar `SIGINT` ao grupo em primeiro plano. `Ctrl+Z` costuma enviar `SIGTSTP` e `fg` retoma um job em primeiro plano. `kill` envia sinais, ou seja não significa sempre matar. Para pedires terminação, começa normalmente com `SIGTERM`, porque `SIGKILL` não permite limpeza pelo programa.

## Manual, permissões e arquivo

`man 2 open` consulta uma chamada de sistema; `man 3 fopen` consulta uma função de biblioteca. `SYNOPSIS` mostra cabeçalhos e tipos. `RETURN VALUE` e `ERRORS` dizem como reconhecer falhas. `command -v gcc` mostra o comando que a shell encontra.

`chmod 640 ficheiro` dá leitura e escrita ao dono, leitura ao grupo e nada aos restantes. O equivalente simbólico é `chmod u=rw,go-rwx,g+r ficheiro`. Os dígitos são combinações de 4 para leitura, 2 para escrita e 1 para execução. Numa linha como `-rw-r-----`, o primeiro carácter indica ficheiro regular e os nove seguintes dão as permissões de dono, grupo e outros. Num diretório, execução permite atravessar nomes; leitura permite listar entradas. Apagar uma entrada depende das permissões do diretório que a contém. A data habitual de `ls -l` é a última modificação, não a criação do ficheiro.

```
tar -czf treino.tar.gz treino
tar -tzf treino.tar.gz
```

O primeiro comando cria um arquivo comprimido e o segundo lista-o sem o extrair. Para exercícios que removem ou substituem ficheiros, trabalha numa pasta de treino e confirma `pwd` antes do comando.

## Exercícios

Argumentos depois da shell

Executas `./programa 7 < dados.txt`. Indica `argc`, `argv[1]` e a origem da entrada padrão.

Pista

O nome do programa conta como argumento.

Mais uma pista

O operador `<` e o seu destino são processados pela shell.

Solução

`argc` vale 2 e argv\[1\] é a string 7. A shell abre `dados.txt` e liga-o ao descritor da entrada padrão. Nem `<` nem `dados.txt` chegam como argumentos neste comando.

Confere a tua resposta:

*   Indiquei argc = 2.
*   Indiquei argv\[1\] como a string 7.
*   Expliquei que stdin lê de dados.txt.

Erros frequentes

Contar o redirecionamento em `argc` ou abrir novamente `stdin` para conseguir ler os dados.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/so/shell-unix/#tr%C3%AAs-canais-e-redirecionamento)

Quem expande o padrão

A pasta tem main.c, ajuda.c e notas.txt. Compara os argumentos que os programas recebem em `printf "%s " *.c` e em `find . -name "*.c" -print`. O que muda com as aspas?

Pista

Pergunta quem vê o asterisco em cada caso.

Mais uma pista

Sem aspas, a shell substitui o padrão pelos dois nomes antes de correr.

Solução

No primeiro, a shell expande o padrão para os nomes ajuda.c e main.c, em ordem dependente da localização. printf recebe o formato e esses dois nomes como argumentos. No segundo, as aspas entregam o padrão ao find, que procura na árvore. Sem aspas, o padrão seria expandido na pasta corrente e o find receberia outros argumentos.

Confere a tua resposta:

*   Indiquei que a shell expande antes de o comando correr.
*   Distingui padrão entre aspas de padrão sem aspas.
*   Previ os argumentos recebidos pelo programa.

Erros frequentes

Achar que o programa recebe o asterisco mesmo sem aspas, ou que o glob é uma expressão regular.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/so/shell-unix/#expandir-nomes-e-proteger-texto)

Ordem dos redirecionamentos

Compara `comando > tudo.txt 2>&1` com `comando 2>&1 > saida.txt`. Para onde vai cada canal em cada caso?

Pista

Lê os operadores da esquerda para a direita.

Mais uma pista

`2>&1` copia o destino atual de stdout nesse instante.

Solução

No primeiro, stdout passa a tudo.txt e stderr copia esse destino, por isso ambos vão para tudo.txt. No segundo, stderr copia o destino antigo de stdout, e só depois stdout muda para saida.txt, por isso stderr conserva o destino anterior. Nunca uses o ficheiro de entrada como destino antes de o programa o ler.

Confere a tua resposta:

*   Descrevi o destino de stdout em cada comando.
*   Descrevi o destino de stderr em cada comando.
*   Expliquei por que a ordem altera o resultado.

Erros frequentes

Dizer que ambas as ordens juntam os canais no mesmo ficheiro.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/so/shell-unix/#tr%C3%AAs-canais-e-redirecionamento)

Seguir campos através de três filtros

O ficheiro `locais.txt` contém estas quatro linhas:

```
porto:12:2
aveiro:5:10
porto:8:1
braga:3:3
```

Prevê o resultado de `cut -d ':' -f 1,3 locais.txt | LC_ALL=C sort | sed 's/:/=/g'`. Mostra a saída de cada filtro. O que passaria a acontecer ao ficheiro de entrada se acrescentasses `> locais.txt` ao fim?

Solução

`cut` conserva `porto:2`, `aveiro:10`, `porto:1` e `braga:3`, nesta ordem. O campo intermédio desaparece. `sort` na localização C produz `aveiro:10`, `braga:3`, `porto:1`, `porto:2`. `sed` substitui o separador e dá:

```
aveiro=10
braga=3
porto=1
porto=2
```

Os filtros não mudam o original. Acrescentar `> locais.txt` cria uma disputa com a abertura de leitura da pipeline: a shell pode truncar o ficheiro antes de `cut` ler os dados. Não há garantia de conservar as quatro linhas. Usa outro destino e só substitui o original depois de validar o resultado.

Confere a tua resposta:

*   Selecionei apenas os campos 1 e 3.
*   Ordenei as linhas pela ordem textual indicada.
*   Substituí o separador na saída sem alterar o original.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/so/shell-unix/#filtrar-e-contar)
