Live Free or Die - John Ringo

GoodReads Summary: When aliens trundled a gate to other worlds into the solar system, the world reacted with awe, hope and fear. But the first aliens to come through, the Glatun, were peaceful traders and the world breathed a sigh of relief. When the Horvath came through, they announced their ownership by dropping rocks on three cities and gutting them. Since then, they've held Terra as their own personal fiefdom. With their control of the orbitals, there's no way to win and earth's governments have accepted the status quo.

Read more  ↩︎

Um Shell por Dia: Jobs

Algumas vezes estamos rodando um comando que é demorado, mas precisamos ter acesso novamente ao shell por algum motivo. Para isso, podemos suspender a execução do processo com as teclas Ctrl+z. Um processo suspendo não executa nada e não consome CPU, mas ainda está presente no sistema -- sendo chamado de "job" nesse ponto.

Para ver a lista de jobs, basta chamar jobs. Note que o resultado desse comando é a lista do nome do processo com um identificador.

Uma vez que eu tenha feito que precisava ser feito e quiser voltar para aplicação, usa-se o comando fg (de "foreground"). Entretanto, eu posso querer que a aplicação continue executando, mas não em primeiro plano; para isso, existe o comando bg (de "background"). Chamados diretamente, fg e bg irão trabalhar com o último comando executado mas é possível indicar um processo específico para estes, usando o identificador apresentado em jobs.

Uma forma de fazer uma aplicação iniciar diretamente em background é usar um & no final da chamada; por exemplo curl url & irá iniciar o curl mas deixar o terminal liberado, pois o comando estará rodando em background.

Seria o mesmo que chamar curl url, usar Ctrl+z para suspender a execução e depois usar bg para fazer o processo rodar em background.

Quando um processo está em background, o terminal fica "bloqueado" para logouts -- pois, afinal de contas, o shell ainda é pai dos processos, mesmo que eles estejam em background. Para passar a guarda de um processo filho para o init -- e, assim, poder ser encerrado sem problemas -- pode-se usar o comando disown. Assim, o processo em background ou suspendo passa a ser filho do init e o terminal pode ser encerrado sem que o processo anterior seja encerrado junto.

Um Shell por Dia: Comandos Obscuros

Existem alguns comandos que quase não são usados e por isso são pouco comentados, mas se algum dia precisar, eles estão lá.

Todos os comandos listados aqui são parte do "GNU CoreUtils" e estão presentes na maior parte das distribuições Linux. Se você estiver usando uma distribuição de BSD, possivelmente vai precisar instalar o pacote do CoreUtils antes de poder usar.

Em nenhuma ordem em particular:

  • tac: imprime o arquivo na ordem inversa, mostrando primeiro a última linha e indo até a primeira; é o inverso do cat.
  • head: imprime as primeiras linhas de um arquivo; é o inverso do tail (e, infelizmente, não tem a opção -f para ver se surge alguma coisa no começo do arquivo).
  • nl: imprime cada uma das linhas do arquivo, colocando o número da mesma na frente.
  • shuf: embaralha o conteúdo de entrada; é o inverso do sort.
  • paste: junta conteúdo do arquivo; é o inverso do cut.
  • expand: Converte tabulações por espaços; é o inverso do unexpand.
  • unexpand: Converte espaços por tabulações; é o unverso do expand.
  • shred: apaga arquivos, mas antes de apagar, sobreescreve todo o conteúdo do mesmo no disco, para previnir que o mesmo não possa ser recuperado.
  • tee: recebe o conteúdo e, ao mesmo tempo em que escreve o mesmo no "stdout", envia para um arquivo. Pode ser usado quando se queira salvar o conteúdo intermediário no meio de uma operação com pipes; por exemplo cat | cut | tee temp | sort fará com que o conteúdo do arquivo seja cortado, gravado no arquivo "temp" e passado ao sort ao mesmo tempo, podendo comparar o resultado antes e depois do sort.
  • cal: mostra um calendário. Por padrão, mostra o mês atual, mas é possível usar, por exemplo cal 2021 e ver o calendário do ano inteiro (infelizmente, só mostra o calendário, não é possível adicionar eventos).
  • nohup: captura a execução de uma aplicação, permitindo que a mesma continue executando mesmo depois de perder o terminal. Explicando: Quando o terminal perde sua conexão (podendo ser porque o terminal foi encerrado, porque o emulador de terminal foi fechado ou porque a conexão foi encerrada), o sistema envia um sinal de "Hang UP" (HUP); o nohup captura este sinal e, como o processo executado é filho dele, ele pede para que o init/systemd assuma a paternidade do processo e deixe ele executando. Assim, caso você feche o terminal ou sua conexão ssh seja terminada, mesmo assim o comando vai continuar executando. Deve ser usado como nohup comando.

Um Shell por Dia: Error Chain

Quando um comando é executado, ele "emite" um código para o shell indicando o resultado de sua execução. Por exemplo, se você rodar ls e estiver no Bash, você pode ver o resultado do comando com echo $? -- que será 0, já que o ls conseguiu executar (a não ser que você esteja num diretório que não tem permissão de listar o conteúdo, mas enfim).

Para quem programa/programou em C, deve lembrar das constantes EXIT_SUCCESS e EXIT_FAILURE, que ficavam no final do main(). Bom, SUCCESS tem definido o valor 0 e FAILURE 1, e se você quiser, pode brincar de criar uma aplicação que retorne os dois valores e verificar o resultado com o echo $?.

Algumas aplicações utilizam vários códigos diferentes para indicar que a execução foi terminada por um erro específico.

Mas para encadeamento de erros, só precisamos saber que 0 é "tudo terminou certo" e qualquer coisa diferente disso é "deu erro".

Para encadear comandos, podemos usar && e ||.

&& indica "se o comando anterior terminar em sucesso, execute o próximo comando". Por exemplo ls && cp arq1 arq2 indica que, se o ls terminar com sucesso, então o comando cp (que copia arquivos) será executado; se, por algum motivo, o ls falhasse, o comando cp não seria executado.

|| indica "se o comando anterior falhar, execute o próximo comando". Assim, ls || cp arq1 arq2 faria com que o cp somente fosse executado se o ls falhasse.

E vários comandos podem ser encadeados dessa forma:

ls && cp arq1 dir/arq2 || mkdir dir

Irá fazer o ls; se ele não falhar, executa o cp; se o cp falhar, cria o diretório.

Um Shell por Dia: Sub comandos

Algumas vezes queremos que parte do comando que será executado seja calculado automaticamente e, para isso, temos sub comandos.

Por exemplo, digamos que você queria executar um comando e mandar para um arquivo com a data atual. Existe um comando pronto para mostrar a data atual, date, que permite que o formato de saída seja definido como, por exemplo, date +"%Y%m%d", que gera algo como "20210921".

Apenas para lembrar, você pode querer fazer

comando > 20210921.txt

E amanhã fazer

comando > 20210922.txt

Só que isso significa que não é possível automatizar isso. Quer dizer, não daria, se não fossem os subcomandos. E para usar subcomandos, são usadas crases (ou, no original, "back-ticks"). No nosso exemplo

comando > `date +"%Y%m%d"`

E, assim, podemos automatizar a geração e não se preocupar em ver o dia correto todo dia.

Mas um aviso: não são todos os shells que suportam esse formato. Fish, por exemplo, requer que os subcomandos sejam executando entre parênteses (comando > (date +"%Y%m%d")) e Bash suporta o formato de $(subcomando). Portanto, cuide qual o shell está sendo executado antes de sair usando os ticks.

Um Shell por Dia: Shells

Mais uma interrupção entes de continuarmos vendo coisas de shell. Por que? Porque dependendo do shell que você está usando, as coisas podem mudar daqui pra frente. Até o presente momento, o que vimos de redecionamento e pipes funciona igual em todos.

  • "sh": "sh" é o Shell que deu origem e todos os demais. Ele é básico mais faz o serviço;
  • "Bash": Bash é o shell mais usado por aí, principalmente porque é o padrão nas distribuições no Linux. Além do básico que o "sh" consegue fazer, Bash tem opções de cálculos matemáticos sem precisar usar uma aplicação externa e testes mais sofisticados (que é importante para quando começarmos a ver como criar scripts);
  • "Zsh": Zsh tem algumas (poucas) diferenças no Bash, mas extende um bocado a parte de auto-complete;
  • "Csh": Csh é um dos shells que basicamente morreu por falta de uso, mas ele permite que sejam escritos scripts usando um subset de C;
  • "Tcsh": Tcsh é bem pareciso com o Csh, mas permite que scripts sejam escritos em um subset de TCL;
  • "Nushell": Nushell é um novo shell na área, que resolveu tratar todos os resultados como Dataframes e pode ser usado para análise simples de dados.

E, por últmo, se você estiver pensando em trocar o shell de forma permanente, é possível usar o comando chsh (change shell). A sintaxe é chsh -s <caminho para o shell que você quer> <username>.

Um Shell Por Dia: Pipes

Ainda sobre entradas e saídas, imagine que você queira usar a saída de um arquivo como entrada de outro. Usando apenas direcionamentos, poderíamos fazer:

comando1 > um_arquivo
comando2 < um_arquivo

(ou seja, salvar a saída de comando1 em um arquivo qualquer -- que chamamos de "um_arquivo" aqui -- e usar o conteúdo desse arquivo como entrada para comando2.)

Com pipes, podemos fazer isso diretamente:

comando1 | comando2

O que o pipe (aquele "|") faz é pegar o stdout do comando da esquerda e fazer com que esse seja o stdin do comando da direita.

A ligação pode ser usadas quantas vezes você quiser:

comando1 | comando2 | comando3 | comando4

O stdout de comando1 vai ficar como stdin do comando2; o stdout do comando2 vai ser o stdin do comando3; e stdout do comando3 vai ser o stdin do comando4.

E como o pipe trabalha apenas com stdout, não dá pra fazer algo com stderr. Mas você pode usar redicionamentos para mandar stderr para stdout e aí usar o pipe.

comando1 2>&1 | comando2

Um Shell Por Dia: Redirecionamento

Agora que conhecemos os descritores de entrada, saída e erro, vamos ver como redirecionar a saída dos descritores para arquivos.

Vamos pegar um exemplo simples: python manage.py migrate, um comando que faz parte do Django que altera o banco de dados. Se quisermos mandar a saída do comando para um arquivo, podemos usar >:

python manage.py migrate > saida_migrate.txt

Quando seguido por >, o shell faz com que o stdout de uma aplicação seja enviada para um arquivo e, assim, qualquer saída da aplicação (com exceção do que for escrito no stderr) será mandando para o arquivo.

Ainda, o arquivo é zerado toda vez que houver o redicionamento, e assim, executar o comando acima duas vezes seguida fará com que o arquivo "saida_migrate.txt" fique apenas com o resultado da segunda execução. Para adicionar o conteúdo no final do arquivo ao invés de sobreescrever o conteúdo inteiro, deve-se usar >> ao invés de >.

Mas, mais uma vez, até agora vimos como mandar stdout para um arquivo. Em alguns casos, erros são enviados para stderr ao invés de stdout. Para redirecionar stderr, usa-se 2>.

E, finalmente, aplicações que esperam entrada de dados -- digamos que o nosso comando acima esperasse uma configuração, algo como "Type 'y' to continue" -- poderíamos criar um arquivo com y dentro e mandar isso para o migrate com <:

python manage.py migrate < confirmacao

Até aqui, simples e direto. Existe ainda uma questão: Imagine que você queira que toda a saída de uma aplicação, stdout e stderr, seja enviada para um arquivo. A princípio, você tentaria

python manage.py migrate > arquivo 2> arquivo

O problema desta chamada é que a saída do stdout iria criar o arquivo, e a saída do stderr iria sobreescrever esta saída e mandar o stderr apenas (existe uma complicação entre "descritores" e "arquivo em disco", onde o descritor pode estar escrevendo num arquivo em disco que não existe mais, e para a aplicação tudo continua andando normalmente, embora não pareça porque o arquivo não exista, mas vamos ignorar essa parte aqui) o que podemos fazer é enviar o stderr para stdout e redirecionar o stdout para o arquivo. Para redirecionar stderr para stdout usamos 2>&1 -- 2> é realmente a saída do stdout, e &1 indica que queremos que a saída indicada seja enviada para o descritor "1" -- que é o stdout.

E ainda dá pra usar todo mundo junto:

python manage.py migrate 2>&1 > saida_migrate.txt < confirmaca

Um Shell Por Dia: Entradas e Saídas

Antes de sairmos vendo comandos e coisas do tipo, vamos começar com um conceito básico: entradas e saídas.

Para entradas e saídas em sistemas POSIX (incluindo os Unixes e Linux), existem três "descritores" que estão presentes para todas as aplicações:

  • stdin, que é descritor de entrada de dados (com o identificador "0");
  • stdout, que é o descritor de saída padrão (com o identificador "1"); e
  • stderr, que é o descritor de saída para erros (com o identificador "2").

Por exemplo, quando uma aplicação começa a jogar informações para o usuário, ela está escrevendo no stdout; quando a aplicação fica esperando que o usuário digite alguma coisa, ela fica tentando ler de stdin.

A importância desta informação ficará mais clara mais pra frente, quando começarmos a ver redicionamentos e pipes.

Um Shell Por Dia: Introdução

Assim como eu já tinha feito com "Um Python Por Dia" e "Um Vim Por Dia", eu comecei uma nova série com os colegas de trabalho, chamado "Um Shell Por Dia".

A ideia é passar pequenas pérolas sobre shell, uma por dia. Isso envolve tanto coisas comuns entre shells -- como, por exemplo, redirecionamento de entradas e saídas -- quanto comandos mais comuns -- como, por exemplo, wc ou time.