sábado, 24 de agosto de 2013

Preenchendo com zeros a esquerda para ordenar números


Um determinado arquivo contém números de 1 a 1045, mas foram gravados fora de ordem. Ao tentar ordená-lo com o comando

$ cat num.txt | sort

tive como resposta a listagem de todos os números que começam com 1, em seguida os que começam com 2 e assim por diante. Logo, não é a forma esperada e para resolver esta situação é preciso preencher com 0 (zero) até que cada número tenha 4 algarismos. Em outras palavras, ficará assim:

0001, 0002, ..., 0009, 0010, ..., 0099, 0100, ..., 0999, 1000, ..., 1045

O código-fonte que apresento desta vez é da linguagem C#. No IDE Visual Studio, abra um novo projeto e escolha o Template: Console Application. Esta opção chama o prompt de comando que é exclusivamente texto.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace AjeitaParaNumerar
{
    class Program
    {
        static void Main(string[] args)
        {
            int i, quantidade=0;
            Console.WriteLine("Este programa le um arquivo de nome num.txt e coloca 3 zeros se o numero for menor que 10, 2 zeros se maior do que 9 e menor do que 100 e por fim, 1 zero se maior do que 99 e moneor do que 1000.");
            StreamReader sr = new StreamReader("num.txt");

            string leitura;
            
     while ((leitura = sr.ReadLine()) != null)
            {
                quantidade++;
            }

            sr.Close();

            StreamWriter sw = new StreamWriter("ok.txt");
            sr = new StreamReader("num.txt");
            
            for (i = 0; i < quantidade; i++)
            {
                string[] valor = new string[quantidade];
                valor[i] = sr.ReadLine();
                if (Convert.ToInt32(valor[i]) < 10)
                {
                    valor[i] = valor[i].PadLeft(4, '0');
                    sw.WriteLine(valor[i]);
                }

                else if (Convert.ToInt32(valor[i]) > 9 && Convert.ToInt32(valor[i]) < 100)
                {
                    valor[i] = valor[i].PadLeft(4, '0');
                    sw.WriteLine(valor[i]);
                }

                else if (Convert.ToInt32(valor[i]) > 99 && Convert.ToInt32(valor[i]) < 1000)
                {
                    valor[i] = valor[i].PadLeft(4, '0');
                    sw.WriteLine(valor[i]);
                }


                else
                {
                    sw.WriteLine(valor[i]);
                }
            }

            sr.Close();
            sw.Close();
        }
    }
}

Mesmo que o código tenha sido desenvolvido em outro sistema operacional, é possível executá-lo no Linux. Para tal, instale o pacote mono-mcs e em seguida execute a linha de comando com o comando mcs com a seguinte sintaxe:

$ mcs fonte.cs -o nome_do_arquivo

Exemplificando supondo que eu escolhi que nome_do_arquivo seja ajeita.out:

$ mcs Program.cs -o ajeita.out

Como resultado, o arquivo ajeita.out não é executável e para torná-lo, basta usar o procedimento usual de dar permissão de execução:

$ chmod a+x ajeita.out

Finalmente, para executá-lo, faça simplesmente:

$ ./ajeita.out

Ao fim da execução, o arquivo ok.txt foi criado e seu conteúdo é a listagem dos 1045 números com quatro algarismos.

Conclusão: O projeto mono permite uma incrível portabilidade para o Linux. Há muitas facilidades por causa dos métodos prontos do C# e por esta razão, torna o desenvolvimento rápido e capaz de funcionar nos dois sistemas operacionais.

Adicionalmente, ao executar o comando para ordenar os números (cat ok.txt | sort), funcionou perfeitamente.

segunda-feira, 22 de julho de 2013

Backup entre pen-drive e HD

Sempre fiz o mesmo procedimento com relação aos trabalhos feitos na faculdade: o que era produzido nos laboratórios era armazenando no meu pen-drive e este conteúdo copiado para o meu computador pessoal. O problema é que às vezes eu esquecia de fazer a segunda etapa e ao longo de um semestre é certo acumular uma quantidade expressiva. Simplesmente copiar todos os trabalhos pode sobreescrever algum diretório e assim, perder algum trabalho. A solução é executar o script abaixo.

As variáveis origem e destino representam, respectivamente, o ponto de montagem do pen-drive e o meu userspace o qual armazeno meus dados. É importante que os subdiretórios abaixo deles tenham o mesma estruturação:

 $ find -maxdepth 1
.
./laboratorio_de_hardware
./Dev-Cpp 5.2.0.3 IDE Only Portable.7z
./engenharia_de_software
./ra_ads.txt
./minicurso_C
./sistemas_operacionais
./ihc
./php
./estatistica
./horario_de_aulas.odt
./contabilidade

O primeiro passo é fazer todo levantamento dos diretórios e arquivos da origem e destino.

Em seguida, o script separa o que é comum a ambos dentro do arquivo /tmp/comum.txt e o que precisa ser copiado em /tmp/inexistente.txt.

Se há algo a ser copiado são exibidas mensagens contabilizando quantos arquivos são comuns, quantos serão copiados e a quantidade em kB a ser copiado. Todos os diretórios que não existem na variável destino (computador) são criados pois causa um erro se tentar copiar para um diretório que não existe. Depois, é necessário preparar um arquivo que contenha o caminho dos arquivos que serão copiados para o HD. Para tal fim, o sed substitui o valor de $origem por $destino no arquivo /tmp/inexistente.txt. Desta forma, tudo que não é comum ao HD e ao pen-drive está pronto pronto para ser copiado.

Se não houver diferença entre origem e destino, exibe a mensagem: "$origem" e "$destino" têm os mesmos arquivos.

#!/bin/bash
rm /tmp/*.txt

origem="/media/murilo/FUJITA/fatec"
destino="/home/murilo/fatec/periodo2-1s_2013"

cd "$destino" 
find -type f > /tmp/fatec_hd.txt
cd "$origem" 
find -type f > /tmp/fatec_pendrive.txt

# Tem arquivo no pen-drive que nao existe no HD?
pen_drive_linhas=`wc -l /tmp/fatec_pendrive.txt | cut -f1 -d" "`
for i in `seq $pen_drive_linhas`
  do
    pendrive_arquivo=`head -n $i /tmp/fatec_pendrive.txt | tail -n 1`
    grep "$pendrive_arquivo" /tmp/fatec_hd.txt
    if [ $? -eq 0 ]
      then 
        echo "$pendrive_arquivo" >> /tmp/comum.txt
      else
        echo "$pendrive_arquivo" >> /tmp/inexistente.txt
    fi
  done

if [ -f /tmp/inexistente.txt ]
  then
    # Full path for source
    sed "s,^.,$origem," /tmp/inexistente.txt >> /tmp/inexistente1.txt
    sed 's/^/"/g' /tmp/inexistente1.txt >> /tmp/$$
    sed 's/$/"/g' /tmp/$$ >> /tmp/inexistente3.txt
    rm /tmp/$$
    
    inexistente=`wc -l /tmp/inexistente1.txt | cut -f1 -d" "`
    
    echo `wc -l /tmp/comum.txt | cut -f1 -d" "` arquivos comuns.
    echo $inexistente arquivos que estao no pen-drive e nao estão no HD.
    echo `wc -l /tmp/fatec_pendrive.txt | cut -f1 -d" "` no total.
    
    # Quantidade em kB a ser copiado do pen-drive para o HD
    for i in `seq $inexistente`
      do 
        arquivo_inexistente=`head -n $i /tmp/inexistente1.txt | tail -n 1`
        du -m "$arquivo_inexistente" | awk '{printf "%s\n",$1}' 
      done > /tmp/kB_arquivos.txt
    
    echo `paste -s -d"+" /tmp/kB_arquivos.txt | bc` kB a ser copiado do pen-drive para o HD
    sleep 2
    
    sed "s,^,$origem," /tmp/inexistente1.txt >> /tmp/inexistente2.txt
    sed 's/$/"/g' /tmp/inexistente2.txt >> /tmp/inexistente3.txt
    
    # Checking for absent directories
    for i in `seq $inexistente`
      do
        inexistente_diretorio=`head -n $i /tmp/inexistente3.txt | tail -n 1 | sed 's/"//g'`
        /usr/bin/dirname "$inexistente_diretorio" 
      done >> /tmp/inexistente_diretorio.txt
    
    sort /tmp/inexistente_diretorio.txt >> /tmp/inexistente_diretorio2.txt
    uniq /tmp/inexistente_diretorio2.txt >> /tmp/inexistente_diretorio3.txt
    sed "s,$origem,$destino," /tmp/inexistente_diretorio3.txt >> /tmp/inexistente_diretorio4.txt
    
    echo Criando `wc -l /tmp/inexistente_diretorio4.txt | cut -f1 -d" "` diretorios.
    sleep 2
    
    #Creating the absent directories in destination (hd)
    int_diretory2create=`wc -l /tmp/inexistente_diretorio4.txt | cut -f1 -d" "`
    for i in `seq $int_diretory2create`
      do
        diretory2create=`head -n $i /tmp/inexistente_diretorio4.txt | tail -n 1`
        mkdir -p "$diretory2create"
      done
    
    # replacing source path for destination path
    sed "s,$origem,$destino," /tmp/inexistente1.txt >> /tmp/inexistente_destino1.txt 
    
    # performs the copy process from pen-drive to hd
    for i in `seq $inexistente`
      do
        from=`head -n $i /tmp/inexistente1.txt | tail -n 1`
        destination=`head -n $i /tmp/inexistente_destino1.txt | tail -n 1`
        cp -v "$from" "$destination"
      done 
  else
     echo
     echo
     echo "$origem" e "$destino" têm os mesmos arquivos
fi

Suprimindo a saída com a longa listagem de arquivos, estes são os dados obtidos pelo script e o tempo para realizar as tarefas de levantar os dados da origem e destino, comparar o que precisa ser copiado, criar os todos os diretórios e copiar nos seus devidos lugares:

381 arquivos comuns.
284 arquivos que estao no pen-drive e nao estão no HD.
665 no total.
301 kB a ser copiado do pen-drive para o HD
Criando 51 diretorios.

real 0m9.874s
user 0m0.300s
sys 0m0.844s

Ao executar novamente, a seguinte resposta é obtida:

/media/murilo/FUJITA/fatec e /home/murilo/fatec/periodo2-1s_2013 têm os mesmos arquivos

Conclusão: Não importa quantos arquivos tenham se acumulados ao longo do tempo, este script permite sincronizar os conteúdos em um intervalo de tempo extremamente curto.

quarta-feira, 26 de junho de 2013

Comparando 2 arquivos de textos e procurando nomes repetidos

Para desenvolver um software é necessário desenvolver ferramentas de testes e assim, mais softwares são desenvolvidos.

Ao executar o comando find procurando arquivos *.JPG e *.jpg, obtém-se como resposta 633 arquivos.

O problema era o fato do software informar 579 arquivos de imagens. A diferença é 54 unidades.

O primeiro passo é comparar as duas listagens de arquivos e entender o que torna o conteúdo distinto. Como o pen-drive é do tipo VFAT, não tinha permissão para executar scripts, então a solução foi usar a linha de comando e direcionar a saída para um arquivo:

$ ls -R | grep '\./' | cut -c 3- | sed 's/://g' > ~/programas/arq2.txt

Em seguida foi feita uma ordenação no arquivo arq2.txt que a saída foi para arq633.txt:

$ cat arq2.txt | sort > arq633.txt

O arquivo arq633.txt contém 633 linhas e cada uma delas o nome do arquivo *.jpg ou *.JPG

O arquivo com as 579 linhas foi gerado pelo software que estou desenvolvendo. É preciso usar o editor vim para eliminar colunas como data, tamanho do arquivo etc.

O script que levanta quais são os arquivos que diferem está abaixo.

#!/bin/bash

ls repeated_files.txt > /dev/null
if [ $? = 0 ]
  then
    rm repeated_files.txt
fi

for i in `seq 633`
  do
    arq633=`head -n $i arq633.txt | tail -n 1`
    freq=`grep "$arq633" arq633.txt | wc -l | awk '{printf "%s\n",$1}'`
    if [ $freq != 1 ]
      then
        grep ^"$arq633" arq633.txt >> repeated_files.txt
    fi
  done

cat repeated_files.txt | sort | uniq

Comento sobre a linha

grep ^"$arq633" arq633.txt >> repeated_files.txt

Sem o circunflexo (^) não há distinção para a ocorrência

1.jpg
baixa11.jpg
baixa1.jpg
CIMG3701.jpg
CIMG3711.jpg
CIMG3751.jpg
CIMG4531.jpg

porque o grep encontra "1.jpg" em todos os arquivos.

Executando o script obtém-se a seguinte saída:

1.jpg
DSC07857.JPG
DSC07858.JPG
DSC07859.JPG
DSC07861.JPG
DSC07862.JPG
DSC07863.JPG
DSC07864.JPG
DSC07865.JPG
DSC07866.JPG
DSC07867.JPG
DSC07868.JPG
DSC07869.JPG
DSC07870.JPG
DSC07871.JPG
DSC07872.JPG
DSC07873.JPG
DSC07874.JPG
DSC07875.JPG
DSC07876.JPG
DSC07877.JPG
DSC07878.JPG
DSC07879.JPG
DSC07880.JPG
DSC07881.JPG
DSC07882.JPG
DSC07883.JPG
DSC07884.JPG
DSC07885.JPG
DSC07886.JPG
DSC07887.JPG
DSC07888.JPG
DSC07889.JPG
DSC07890.JPG
DSC07891.JPG
DSC07892.JPG
DSC07893.JPG
DSC07894.JPG
DSC07895.JPG
DSC07896.JPG
DSC07897.JPG
DSC07898.JPG
DSC07899.JPG
DSC07900.JPG
DSC07901.JPG
DSC07902.JPG
DSC07903.JPG
DSC07904.JPG
DSC07905.JPG
DSC07906.JPG
DSC07907.JPG
DSC07908.JPG
DSC07909.JPG
DSC07910.JPG

Que são 54 arquivos JPG.

Conclusão: O software processa 633 imagens, mas como há nomes repetidos, 54 são sobreescritos mostrando 579 arquivos JPG/jpg.

Caso encerrado!

segunda-feira, 1 de abril de 2013

Criando tabela em arquivo HTML usando C

Criando tabelas HTML através da linguagem C

Em uma aula de linguagem de programação, foi proposto o seguinte exercício de lógica:

O usuário deve entrar com a quantidade de linhas e colunas e com estes dados, desenhar um retângulo que a borda é o caracter 'O' e o seu interior é preenchido com 'X'.

Por exemplo, se a entrada fosse 3 linhas e 5 colunas, teríamos:

OOOOO
OXXXO
OOOOO

Assim, tive a ideia de fazer um programa em C que fizesse a estrutura de uma tabela HTML pedindo para o usuário a quantidade de linhas e colunas.

O ponteiro para arquivos ps cria o arquivo tabelas.html e ao invés de usar printf que imprime na tela, usa-se fprintf para imprimir dentro do arquivo.

São necessários dois laços for sendo que o for externo diz respeito à linha e o mais interno, à coluna.

Uma vez que o ponteiro é aberto para escrita, deve-se instruí-lo a fechá-lo e o comando para este fim é o fclose(nome_do_ponteiro). Neste exemplo, fclose(ps).

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int i,j,colunas=1, linhas=1, contador=0;
    FILE *ps;

    ps = fopen("tabela.html","w");
    if (ps == NULL)
    {
        printf("Erro na abertura do arquivo");
        exit(0);
    }

    printf("Programa de geracao de tabelas\n\n");
    printf("Digite o numero de linhas da tabela: ");
    scanf("%d",&linhas);
    printf("Digite o numero de colunas da tabela: ");
    scanf("%d",&colunas);

    fprintf(ps,"<html>");
    fprintf(ps,"\n");
    fprintf(ps," <head>");
    fprintf(ps,"\n");
    fprintf(ps,"   <title></title>\n");
    fprintf(ps," </head>");
    fprintf(ps,"\n\n");

    fprintf(ps,"  <body>\n\n");
    fprintf(ps,"  <table>\n");

    for (i=1; i<=linhas;i++)
    {
        for (j=1; j<=colunas+2; j++)
        {
              if (j==1)
                fprintf(ps,"   <tr> ");
              else if (j==colunas+2)
                fprintf(ps," </tr>\n");

              else
                fprintf(ps," <td></td> ");
              contador++;              
        }
    }

    fprintf(ps,"  </table>\n");
    fprintf(ps,"\n");

    fprintf(ps,"  </body>\n");
    fprintf(ps,"</html>");

    fclose(ps);

    printf("Arquivo tabelas.html criado com sucesso\n");

    return 0;
}

Se estiver usando uma distribuição GNU/Linux, compile com:

$ gcc tabelaHTML.c -o tabelaHTML.out

Em seguida, execute da seguinte forma:

$ ./tabelaHTML.out

Como diz a mensagem, o arquivo tabela.html foi criado com sucesso. Abra este arquivo que está no mesmo diretório que o seu binário e bom proveito!

sábado, 2 de março de 2013

Ponteiro tipo char para imprimir um número indeterminado de caracteres

Uma habilidade muito comum e exigido na linguagem C é a manipulação de caracteres. Como a declaração da variável é do tipo char, é preciso tratar caracter a caracter ao invés da string toda.

O exemplo abaixo declara um ponteiro do tipo char e é atribuído palavras, portanto, contém inclusive espaços.

O laço while analisa o indíce do ponteiro que incrementado antes do fim do bloco verificando se chegou no fim da cadeia de caracteres indicada por '\0'.

A cada vez que o caracter é impresso, o comando printf adiciona um espaço pela ação de "%c ".

# include <stdio.h>
# include <unistd.h>

int main()
{
  int i;
  char *vetor;

  vetor="Este ponteiro de nome vetor passa a armazenar uma frase que poderia ser muito maior do que esta.";
  
  i=0;
  while (vetor[i] != '\0')
  {
    printf ("%c ", vetor[i]);
    i++;    
  }

  printf("\n");

  return 0;
}

O resultado é exibido abaixo:

E s t e p o n t e i r o d e n o m e v e t o r p a s s a a a r m a z e n a r u m a f r a s e q u e p o d e r i a s e r m u i t o m a i o r d o q u e e s t a .

sábado, 2 de fevereiro de 2013

Exibição dos caracteres especiais para HTML

O fato de desenvolver textos HTML tanto para Linux como para Windows tem causado o incoveniente de exibir os caracteres acentuados de forma errada porque os sistemas operacionais usam codificações distintas.

A melhor forma de adequar a ambas as situações é substituir os caracteres problemáticos por códigos que ao serem interpretados pelo navegador são traduzidos pela combinação de vogais acentuadas ou cedilha.

O código abaixo faz a substituição global destes caracteres. Desta forma, basta redigir o texto normalmente e após a execução do script o texto está pronto para ser visualizado por qualquer navegador.

Observação: É preciso juntar o "&" com o que vem em seguida, pois se fosse feito no editor seria interpretado e desta forma não seria exibido o código HTML.

#/bin/bash
# Fonte dos caracteres especiais: http://www.lsi.usp.br/~help/html/iso.html

if [ $# != 1 ]
  then
    echo Digite $0 arq
    exit
  else
    sed 's/Á/\& Aacute;/g' $1 > $$
    sed 's/á/\& aacute;/g' $$ > $1
    sed 's/Â/\& Acirc;/g' $1 > $$
    sed 's/â/\& acirc;/g' $$ > $1
    sed 's/À/\& Agrave;/g' $1 > $$
    sed 's/à/\& agrave;/g' $$ > $1
    sed 's/Â/\& Atilde;/g' $1 > $$
    sed 's/ã/\& atilde;/g' $$ > $1

    sed 's/É/\& Eacute;/g' $1 > $$
    sed 's/é/\& eacute;/g' $$ > $1
    sed 's/Ê/\& Ecirc;/g' $1 > $$
    sed 's/ê/\& ecirc;/g' $$ > $1

    sed 's/Í/\& Iacute;/g' $1 > $$
    sed 's/í/\& iacute;/g' $$ > $1

    sed 's/Ó/\& Oacute;/g' $1 > $$
    sed 's/ó/\& oacute;/g' $$ > $1
    sed 's/Ô/\& Ocirc;/g' $1 > $$
    sed 's/ô/\& ocirc;/g' $$ > $1
    sed 's/Õ/\& Otilde;/g' $1 > $$
    sed 's/õ/\& otilde;/g' $$ > $1

    sed 's/Ú/\& Uacute;/g' $1 > $$
    sed 's/ú/\& uacute;/g' $$ > $1

    sed 's/ç/\& ccedil;/g' $1 > $$

    sed 's/>/\&# 62;/g' $$ > $1
    sed 's/</\&# 60;/g' $1 > $$
    
fi
mv -v $$ $1