inSANE - Escaneando Coisas de uma forma Preguiçosa

󰃭 07 Nov 2025 (updated: 07 Nov 2025 )

Era Novembro de 2024, e fazia menos de um mês que eu tinha migrado para o Linux, até que aconteceu um problema: O Epson Scan 2, que é o programa que eu usava para usar o Scanner da minha impressora, parou de funcionar.

Depois fui para uma solução do próprio Gnome: O Simple Scan. Funcionou muito bem, até escanear um Arquivo em PDF. E o Simple Scan passou a salvar tudo como um PDF, o que pra mim era inútil.

Depois tentei o xSANE via AUR. Ele é horroroso, mas funcionou bem. Porém, como eu tenho uma política de “AUR apenas em último caso”, acabei desinstalando ele.

E pesquisando uma alternativa, acabei encontrando uma página na Arch Wiki explicando sobre o scanimage, que funciona por linha de comando e como sou um preguiçoso, decidi fazer um Script.

scanimage: O Coração do Script

O comando do scanimage é bem simples, sendo assim.

scanimage --device "pixma:04A91749_247936" --format=tiff --output-file test.tiff

E que tem os seguintes argumentos:

  • -- device: Aqui é o dispositivo que será usado para fazer o escaneamento;
  • -- format: Aqui é o formato do arquivo do escaneamento;
  • -- output-file: Aqui vai o nome do arquivo de saída.

Bem simples, né? Só colocar o ID do dispositivo, o formato da imagem e o arquivo de saída.

Obtendo o ID do Dispositivo

Mas, como obter o ID do dispositivo? Nesse caso, é usado o comando scanimage -L, que retorna os IDs dos dispositivos que foram encontrados.

É assim que o ScanImage mostra os Scanners. Parece muito fácil…

Nesse caso, é só pegar o ID do Scanner; no caso acima, o `epsonds:libusb:001:023'; e usar o comando, certo? Bom, isso funciona até certo ponto. Quando o Scanner é desconectado e reconectado, o ID muda.

… mas o ID do Scanner muda toda vez que o USB é Desconectado e Reconectado

Para resolver isso, inicialmente, usei esse comando aqui:

scanimage -L | grep -v Camera | awk -F '`' -P '{ print $2 }' | cut -d"'" -f1

Mas o que ele faz? Como demonstrado, o scanimage -L detecta os dispositivos que podem ser usados com o SANE. Mas, aparece um dispositivo de câmera, não quero ter isso. Quero usar o Scanner.

Com isso, vem o | grep -v Camera que redireciona a lista que foi gerada para o grep, que basicamente filtra palavras e expressões da saída de um comando. E no grep, foi usado o -v, que retira da saída do comando, as linhas que contém a palavra ou a frase que estou procurando.

Como vimos, tem um v41:/dev/video0. Precisamos retirar isso, e foi o que fizemos, porém a saída ainda está meio suja

Porém, isso não é o suficiente. Essa saída ainda precisa de polimentos. Com isso, vem mais um comando: o | awk -F '`' -P '{ print $2 }'. Esse comando, basicamente ele retira tudo o que está antes do `. Nesse caso, o device `.

Limpamos um pouco a saída, mas tem trabalho a ser feito.

Isso não é o suficiente. Quero apenas o ID. o “is a Epson L3150 Series ESC/I-2” é completamente inútil no comando e isso tem que ser retirado. Com isso, vem o último comando dessa cadeia, o | cut -d"'" -f1. Basicamente, esse comando corta tudo o que está depois do ', deixando apenas o que é necessário.

Essa é a saída totalmente limpa. É disso que precisamos para o comando. Mas dá pra melhorar.

E com isso, temos o ID do Scanner.

Melhorando a Detecção do Scanner

Porém, ao testar em algumas distribuições Linux, tive problemas, principalmente no Linux Mint, que configura a minha Epson como um Scanner de Rede.

Tive que arrumar um jeito para fazer o inSANE funcionar exclusivamente com Scanners USB. Com isso, recorri a outro comando:

scannerID=$(sane-find-scanner | grep possible | awk '{print $NF}')

E o que esse comando faz? Ele usa o sane-find-scanner para detectar possíveis Scanners conectados nas portas USB do computador. A saída desse comando, é essa aqui:

O sane-find-scanner escaneia por todas as saídas USB em busca de dispositivos que possam ser um Scanner…

Basicamente ele pesquisa por todas as portas USB, e tenta ver se aquilo é um Scanner. Ao menos, é assim que parece que ele funciona. Com isso, vem outro comando para poder filtrar isso melhor: o | grep possible. Esse comando irá filtrar pelas linhas que estão marcadas como possible. O que diminui muito a saída.

… mas assim como o scanimage -L, dá pra limpar a saída desse comando. Comecemos pelo o que realmente pode ser um Scanner…

E por fim, é usado o comando | awk '{print $NF}' para poder extrair apenas o ID da USB. Esse awk extrai apenas a última coisa que há na linha.

… para assim, obter parte do ID do Scanner.

Porém esse ID está incompleto. Com isso, ele é guardado na variável scannerID e essa variável é usada em outro lugar: Naquele comando em que era utilizado anteriormente para poder detectar o Scanner. Ou seja, ao invés do comando ficar assim…

scanimage -L | grep -v Camera | awk -F '`' -P '{ print $2 }' | cut -d"'" -f1

… ele fica assim.

scanimage -L | grep $scannerID | awk -F '`' '{ print $2 }' | cut -d"'" -f1

Agora, o inSANE está funcionando exclusivamente com Scanners USB. Mas, pode ser que eu tenha que melhorar mais essa parte do Script.

Configurando as Variáveis Padrão do Script

A primeira coisa que foi necessário fazer no Script, foi definir algumas variáveis, como a Pasta Padrão, a Pasta de Imagens e a Resolução Padrão.

configurarVariaveis () {
	PastaImagens=$(xdg-user-dir PICTURES)
	PastaPadrao=$PastaImagens/Scan ## Pasta onde o Scan será salvo
	ResolucaoPadrao=600 ## Resolução da imagem em DPI
}

Optei por separar a variável da Pasta Padrão em duas: Uma para detectar a pasta de imagens do Usuário com o comando xdg-user-dir PICTURES e a outra com o resultado da variável anterior com a adição da pasta Scan.

Talvez isso não tenha sido necessário, mas optei por fazer para que, pra mim, o Script ficasse mais fácil de entender.

Já a Resolução, escolhi 600 DPI, pois é a Resolução que acho mais adequada na hora de fazer a Linework do desenho, pois não preciso ficar dando Zoom na imagem. Um 100% de Zoom, já basta em muitos dos casos.

Lendo Arquivos de Configuração

Mas e se eu precisar escanear em uma pasta diferente com uma resolução menor? É para isso que implementei um suporte a arquivos de configuração no inSANE. Basta criar um arquivo .insane.conf na sua pasta pessoal nesse modelo…

Pasta=~/Imagens/Escaneados
Resolucao=300

… que tudo funciona, conforme o que foi colocado no .insane.conf. Mas como isso funciona? É a rotina arquivoConfiguracao que faz isso.

configurarVariaveis
echo "Verificando Arquivo de Configuração..."
config=~/.insane.conf ## Arquivo de Configuração Padrão

Primeiramente, ela carrega a função carregarVariaveis, que define as variáveis padrão da Pasta e Resolução. Isso serve como um Backup, no caso de alguma das variáveis não existir no Arquivo e aqui é definido o nome do Arquivo de configuração.

Depois, é feito um teste para ver se o Arquivo existe.

if [ -e "$config" ]; then 

E se o Arquivo existir, ele é carregado com o comando source…

echo "Lendo arquivo de configuração..."
source $config 
echo -e "Verificando variáveis...\n"

… e logo as variáveis começam a ser verificadas, começando pela variável da Pasta onde os Arquivos Escaneados irão e depois, indo para a Resolução.

if [ -z "$Pasta" ]; then 
	echo -e "\nPasta... Erro na Configuração: Esse parâmetro não está configurado\nUsando o parâmetro padrão da Pasta..."
	Pasta=$PastaPadrao
	echo -e "	Pasta de Escaneamento:" "$Pasta" "\n"
else
	echo -e "	Pasta... OK \nPasta de Escaneamento:" "$Pasta" "\n"
fi

if [ -z "$Resolucao" ]; then
	echo -e "	Resolução... Erro na Configuração: Esse parâmetro não está configurado\n	Usando parâmetro padrão da Resolução..."
	Resolucao=$ResolucaoPadrao
	echo  -e "Resolução:" $Resolucao "\n"
else
	echo  -e "Resolução... OK\n	Resolução:" "$Resolucao" "\n"
fi

pastaScan

Como o source carrega o arquivo de configuração, as Variáveis já estão carregadas. Então, o que o if [ -z ] verifica é se a variável está vazia. Se elas não estiverem vazias, isso significa que elas vieram do Arquivo de Configuração, então são elas que serão usadas.

E se elas estiverem vazias, isso significa que elas são estão carregadas, e com isso as variáveis padrão serão carregadas. Mas e se o arquivo de configuração não existir? Nesse caso, é executado esse bloco de comandos, onde as Variáveis são definidas como as padrões.

echo "Usando configurações padrões..."
Pasta=$PastaPadrao
Resolucao=$ResolucaoPadrao
echo -e  "	Pasta de Escaneamento:" "$Pasta" "\n	Resolução:" $Resolucao "\n"
pastaScan

E nos dois casos, o inSANE irá chamar outra função para verificar se a pasta de Scans existe. E é um código bem simples pra isso.

pastaScan () {
	echo "Verificando pasta de Scans..."
	if [ -d "$Pasta" ]; then # Verificando se a pasta existe
		echo "Pasta de Escaneamento... OK" # Jogando a saída fora para prosseguir com o Script
	else
		echo "Criando a pasta de Escaneamento..."
		mkdir "$Pasta" # Se não existe, criar a pasta
	fi
}

Primeiramente, o inSANE verifica se a pasta existe. Se ela existir, ele prossegue, e se não existir, a pasta é criada automaticamente.

Yay! Notificações

Isso foi algo que eu quis incrementar no inSANE, principalmente para saber se o Escaneamento começou, qual Scanner foi detectado e quando terminou. Eu poderia apenas executar o inSANE por linha de comando, mas tem aqueles momentos que eu não quero isso.

E como fazer isso? Usei o mesmo if [ -z para verificar se o notify-send existe. Só que, ao invés de usar uma variável, estou usando o comando command -v, que verifica se um determinado comando existe no sistema.

if [ -z "$(command -v notify-send)" ]; then

E se o comando existe, ele irá retornar o caminho do comando e se não existir, ele vai retornar uma saída em branco.

Um exemplo do if testando se um comando existe, mas aqui com um existente Um exemplo do if testando se um comando existe, mas aqui com um inexistente

Tive que fazer isso, pois em algumas distros esse comando não existe por padrão. E queria ter uma forma do Script funcionar sem isso. Ou seja, se o comando não existir, o Script apenas irá prosseguir, jogando a saída para o /dev/null.

echo "" > /dev/null

Tipos de Notificação

Mas se o comando existir, começa uma série de if e elif para verificar qual foi a entrada que foi dada para essa função, pois fiz ela para ter algumas variantes. E as variantes são:

  • Detecção do Scanner: detectandoScanner;
  • Scanner Detectado: scannerDetectado;
  • Escaneamento em Progresso: escaneando;
  • Escaneamento Finalizado: escaneamentoConcluido;
  • SANE não Encontrado: faltaSANE;
  • Scanner não Encontrado: erroScanner.

Detecção do Scanner

Se a função receber a entrada de que a Detecção do Scanner está ocorrendo, ela invoca esse comando…

notify-send -a "inSANE" -i scanner "Aguarde..." "Detectando Scanner..."

… que dá nessa notificação.

Notificação do inSANE detectando algum Scanner

Scanner Detectado

Se a função receber a entrada de que o Scanner foi detectado, ela invoca esse comando…

notify-send -a "inSANE" -i scanner "Scanner Detectado!" "Foi detectado o scanner $Scanner"

… o que dá nessa notificação, que informa que o Scanner foi detectado e utiliza o ID do Scanner para mostrar qual está sendo usado.

Notificação do inSANE quando ele detecta o Scanner

Escaneamento em Progresso

Se a função receber a entrada de que o Escaneamento está em Progresso, ela invoca esse comando…

notify-send -a "inSANE" -i scanner "Aguarde..." "Escaneando a imagem em $Resolucao DPI na pasta $Pasta"

… que dá nessa notificação, onde informa o local em que a imagem está sendo escaneada e em qual resolução.

Notificação do inSANE  enquanto ele está escaneando alguma imagem

Escaneamento Finalizado

Para a entrada “Escaneamento Finalizado”, a coisa é um pouco diferente. O que é invocado dessa vez, é esse bloco de comando…

AbrirImagem=$(notify-send -a "inSANE" "Escaneamento Concluído!" "Imagem escaneada com sucesso!" -i image --action="Abrir a Imagem Escaneada" -u critical) > /dev/null 

	case $AbrirImagem in # Se clicar no botão para Abrir a Imagem na Notificação...
		"0")
			xdg-open "$Arquivo" 
		;;
	esac

… o que dá nessa notificação

Notificação do inSANE  quando ele conclui um escaneamento

Como eu quis que esse fosse uma notificação que colocasse uma opção para abrir a imagem no Visualizador de Imagens padrão, foi daquela forma que tive que fazer.

Porém, dependendo do sistema de notificações que está sendo utilizado pelo Ambiente Gráfico, a opção de Abrir a Imagem com o Visualizador de Imagens, pode não funcionar ou sequer aparecer, como no caso do dusnt.

SANE não Encontrado!

Sim, também tem notificações para o caso do SANE não ser encontrado. Nesse caso, quando a função recebe a entrada de “SANE não Encontrado”, é invocado esse comando…

notify-send -a "inSANE" -i scanner "Erro! SANE não encontrado!" "O SANE não está instalado. Favor, instalar o SANE."

… que exibe essa notificação.

Notificação do inSANE  quando ele não detecta o SANE

Scanner não Encontrado

E por fim, se a função receber a entrada de que o Scanner não foi entrado, é invocado esse comando…

notify-send -a "inSANE" -i scanner "Erro! Scanner não encontrado!" "Conecte um Scanner compatível na porta USB ou desconecte e reconecte o Scanner."

… que exibe essa notificação.

Notificação do inSANE quando ele não detecta um Scanner USB

Detectando se o SANE está instalado

Em algumas das distros que testei e em outras que eu usei, percebi que o SANE não vem instalado. Com isso, tive a ideia de implementar uma função para verificar se o SANE está instalado. E essa função utiliza a mesma base da que verifica se o notify-send está instalado.

verificarSANE () {
	if [ -z "$(command -v scanimage)" ]; then ## Verificando se o SANE está instalado
		echo "O SANE não está instalado. Favor, instalar o SANE antes de prosseguir."
		notificacoes faltaSANE
	else
		echo "" > /dev/null ## Se o SANE estiver instalado, prossiga
	fi
}

Basicamente, se o SANE não estiver instalado, ela envia uma mensagem de erro no Terminal e uma notificação, informando que falta instalar o SANE. E se estiver instalado, o inSANE prossegue.

Por fim, o Escaneamento!

Depois disso tudo, finalmente vem os últimos comando, que junta tudo para poder escanear as imagens. Primeiramente, uma mensagem de boas vindas…

echo -e "\nBem-vindo ao inSANE\nEsse é um Script simples para usar o Scanner por meio do SANE\nVersão 1.1.0\nScript desenvolvido por Rapoelho\n"

… depois disso, a função que verifica se o SANE está instalado e a que carrega o Arquivo de Configuração e as Variáveis.

verificarSANE
arquivoConfiguracao

echo "Detectando Scanner..."
notificacoes detectandoScanner
Scanner=$(selecionarScanner)

E a Detecção do Scanner dá início, com a variável do Scanner sendo alimentada com a função que detecta o Scanner e a notificação de que a Detecção do Scanner está ocorrendo.

Com isso, o inSANE passa para o próximo passo: Verificar a variável do Scanner.

if [ "$Scanner" == "" ]; then
	echo -e "\nScanner não encontrado!\nPossíveis Soluções:\n	- Conecte um Scanner na porta USB do Computador\n 	- Desconecte e Conecte o Scanner.\n	- Conecte um Scanner compatível com o SANE"
	notificacoes erroScanner
	exit
else
	echo "Foi Detectado o Scanner" "$Scanner""."
	notificacoes scannerDetectado
fi

echo "Escaneando..."
notificacoes escaneando

Basicamente, o que esse pedaço do Script faz, é verificar se a variável está vazia. Se estiver vazia, o inSANE dá uma mensagem de erro, notifica que o Scanner não foi encontrado e encerra.

Porém, se o Scanner for detectado, o inSANE envia uma notificação de que o Scanner foi encontrado e prossegue. Com isso, mais uma variável é carregada: O nome do Arquivo que foi escaneado.

Arquivo="$Pasta/Scan_$(date +"%Y-%m-%d_%H-%M-%S").jpg"

Aqui decidi por um formado que tem o prefixo Scan_ e a data e hora em que o escaneamento está ocorrendo. Optei por esse formato, pois eu estava acostumado com eles e o XnViewMP usava o nome do Arquivo nesse formato. E eu gostava, pois não tinha que pensar muito na hora de nomear o arquivo que foi escaneado.

E bom. Já temos o Scanner, o Nome do Arquivo e a Resolução. É hora de juntar tudo num único comando…

scanimage --device "$Scanner" --format=jpeg --output-file "$Arquivo" --resolution "$Resolucao"

… e finalmente escanear a imagem. Para finalizar, o inSANE verifica se tudo ocorreu bem durante o escaneamento, exibe a notificação de que o Escaneamento terminou e encerra o Script.

if [ $? -eq 0 ]; then
    echo "Imagem escaneada com sucesso em" "$Pasta" "em" "$Resolucao" "DPI"
    notificacoes escaneamentoConcluido &
    exit
fi

Conclusão

Bom, esse é um Script que começou bem simples, mas que se expandiu, e mesmo que ele tenha continuado simples, ele ficou bem completo.

E esse Script demonstra uma coisa que adoro no Linux: O poder do Bash e como ele permite que eu faça as minhas próprias soluções. Que é basicamente o que eu criei nos meus últimos Scripts que postei por aqui.

O inSANE acabou sendo o meu primeiro Script mais complexo e é um Script que uso muito até hoje. Do que surgiu de uma preguiça de ter que reinstalar o sistema, pois os programas que eu usava acabaram dando problema, se tornou uma solução que uso até hoje.