O cat
é um utilitário de linha de comando que lê arquivos, seja apenas um ou uma sequência informada, e escreve o seu conteúdo em fluxo de destino.
O nome cat
é derivado de: (con)catenate files.
Conforme o manual (man cat
) é descrito com duas funções: concatenar arquivos e imprimir o seu conteúdo em um fluxo de saída.
Utilizamos o cat
da seguinte forma:
cat [OPTION]... [FILE]...
Essa implementação é com fim de tentar a resolução, própria, do Exercício 01, da Piscine 10 de C, da Escola 42. Não participei de nenhuma piscina, apenas encontrei os PDFs relativos no Github e estou tentando fazer os exercícios.
São as regras a implementação desta versão:
Como pode se ler das orientações acima, a implementação desta versão tem limitações das funções que poderão ser utilizadas.
Nesta minha implementação, optei por utilizar apenas as funções: open, close, read e write.
Antes de adentrar diretamente na escrita da versão do cat
, vou estar escrevendo algumas notas de introdutórias.
File Descritores
Um ************File Descritor (FD)************ é um número inteiro único maior que zero que guarda referência para um arquivo aberto pelo sistema operacional.
Quando pede ao sistema operacional o acesso a determinado arquivo ou fluxo de dados, o ***kernel*** retorna, um *********File Descritor********* de um registro na tabela global de registros de arquivos do sistema operacional.
Por meio dele o processo ou programa pode acessar os dados constantes no arquivo, ou fluxo referenciado pelo FD. Observa-se que o pedido de acesso tenha sido acatado com sucesso.
Em sistemas baseados em Unix e Linux os primeiros FD referem aos fluxos stdin, stdout e stderr.
Nome | File Descriptor | Descrição | Abreviatura |
---|---|---|---|
Standard input | 0 | O fluxo de dados padrão para entrada, por exemplo, em um pipeline de comando. No terminal, o padrão é a entrada do teclado do usuário. | stdin |
Standard output | 1 | O fluxo de dados padrão para saída, por exemplo, quando um comando imprime texto. No terminal, o padrão é a tela do usuário. | stdout |
Standard error | 2 | O fluxo de dados padrão para a saída relacionada à ocorrência de um erro. No terminal, o padrão é a tela do usuário. | stderr |
Os valores dos FD acima descritos podem serem acessados indicando diretamente os seus valores ou por meio de constantes presentes nas bibliotecas <unistd.h>
e <stdio.h>
.
STDIN_FILENO | stdin |
STDOUT_FILENO | stdout |
STDOUT_FILENO | stderr |
Outra questão referente aos FD é a possibilidade de redirecionar os fluxos de dados entre diferentes FD por meio do *pipe*. Mas esse tópico tratarei em outro momento.
Referências:
Função open()
A função open()
permite a realização de conexão entre um arquivo e um FD. Por meio dessa função, o programa tem acesso ao FD de um arquivo informado como parâmetro na chamada da função.
A função tem o seguinte protótipo:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
E é disponibilizada pela biblioteca <fcntl.h>
.
O primeiro argumento, path, é pelo qual passamos o caminho do caminho ou fluxo de dados que queremos acessar/abrir.
O segundo argumento é o tipo de acesso que desejamos(leitura, escrita, leitura e escrita, etc). Esse passamos informando a respectiva, quis são constantes definidas no cabeçalho <fcntl.h>
. Exemplos de algumas **flags** definidas e mais utilizadas.
Flag | Descrição |
---|---|
O_EXEC: | Abre o arquivo para execução, apenas. Não se aplica a diretórios. |
O_RDONLY | Apenas leitura. |
O_RDWR | Abre o arquivo para leitura e escrita. |
O_RDWR | Abre um diretório para pesquisa, apenas. Não se aplica a não diretórios. |
O_WRONLY | Abre o arquivo apenas para a escrita. |
O terceiro argumento, facultativo, é utilizado quando estamos criando um novo arquivo. Pois, é por meio dele que definimos as permissões iniciais do arquivo criado.
***************Retorno:*************** O retorno da chamada da função open()
é um número inteiro positivo que representa FD do respectivo arquivo ou fluxo de dados requerido a ser aberto. Caso contrário, a função retorna -1
e define o tipo de erro que ocorreu.
O código abaixo é a implementação de utilização da função open() que cria, se não existir um arquivo e salva nele que o usuário digitar no stdin
, por enquanto o programa estiver em execução.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_BUF 1024
int main(int argc, char **argv)
{
char *file;
char buf[1024];
int fd;
int flags;
int bytes_reads;
int bytes_writes;
if (argc < 2)
{
perror("Arquivo de destino não definido.\n");
return (1);
}
flags = O_WRONLY | O_CREAT | O_TRUNC;
file = argv[1];
fd = open(file, flags, S_IRUSR | S_IWUSR);
if (fd == -1)
{
perror("Erro ao abrir o arquivo!\n");
return (1);
}
while ((bytes_reads = read(STDIN_FILENO, buf, MAX_BUF - 1)) > 0)
{
bytes_writes = write(fd, buf, bytes_reads);
if (bytes_writes != bytes_reads)
{
perror("Error ao escrever no arquivo\n.");
close(fd);
return (1);
}
}
if (bytes_reads == -1)
{
perror("Erro na leitura do STDIN\n");
close(fd);
return (1);
}
close(fd);
return (0);
}
O código ficou um pouco longo, mas bom que aproveito como exemplo para as explicações abaixo, sobre as demais funções.
Para mais informações: man 2 open
.
Referências:
- https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html
- https://en.wikipedia.org/wiki/Open_(system_call)
Função close()
A função close()
fecha um FD aberto e referenciado. A chamamos quando precisamos fechar um arquivo ou fluxo de dados abertos. Fazemos isso passamos o arquivo ou fonte de dados como parâmetro, no qual será terminado a conexão com o FD referente.
A função tem o seguinte protótipo:
int close(int fd);
E é disponibilizada pela biblioteca <unistd.h>
.
A função retorna 0 no caso de sucesso ou -1, em caso tenha ocorrido algum erro.
Como pose se ver no código anteriormente exposto, a chamamos para fechar o FD do arquivo aberto antes do retorno final da função main
().
Função read()
A função read()
lê uma certa quantidade de bytes a partir de um FD e os salva em um buffer.
Ela tem o seguinte protótipo:
ssize_t read(int fd, void *buf, size_t count);
O primeiro argumento é a indicação do FD no qual será feita a leitura. O segundo é o do buffer que será salvo os dados lidos e o terceiro é a quantidade de **bytes** a serem lidos/transferidos.
E é disponibilizada pela biblioteca <unistd.h>
.
Quanto ao retorno:
Cada chamada retorna uma contagem do número de bytes transferidos. Na leitura, o número de bytes retornados pode ser menor que o número solicitado.
Um valor de retorno de zero bytes implica o fim do arquivo, e -1 indica algum tipo de erro.
Referências: RITCHIE, Dennis M. et al. The C programming language. Bell Sys. Tech. J, v. 57, n. 6, p. 1991-2019, 1978.
Como pode ser observado no código recortado do exposto acima, a função read()
é chamada no contexto de um loop while
. Por enquanto o valor retornado for maior que zero, os dados lidos, por quantidade definida: a vez, são salvos no na variável buf
e valor da quantidade de bytes lidos são salvos na variável bytes_reads
.
Após a execução, é feita uma verificação se ocorreu algum erro na leitura.
while ((bytes_reads = read(STDIN_FILENO, buf, MAX_BUF - 1)) > 0)
{
bytes_writes = write(fd, buf, bytes_reads);
if (bytes_writes != bytes_reads)
{
perror("Error ao escrever no arquivo\n.");
close(fd);
return (1);
}
}
if (bytes_reads == -1)
{
perror("Erro na leitura do STDIN\n");
close(fd);
return (1);
}
Função write()
A função write()
escreve uma quantidade n
de bytes provenientes de um buffer em um FD informado.
Ela tem o seguinte protótipo:
ssize_t write(int fd, const void *buf, size_t count);
O primeiro argumento é o FD onde serão escritos os dados, o segundo é do ***buffer*** no qual serão provenientes e o terceiro é quantidade de **bytes** que serão escritos por vez.
A função é disponibilizada pela biblioteca <unistd.h>
.
Quanto ao retorno:
Para gravação, o valor de retorno é o número de bytes gravados. Ocorreu um erro se esse número não for igual ao número solicitado.
Referências: RITCHIE, Dennis M. et al. The C programming language. Bell Sys. Tech. J, v. 57, n. 6, p. 1991-2019, 1978.
Implementação de uma versão simples do utilitário cat
Escrita as breves notas acima, agora é vez de escrever sobre a implementação do utilitário cat
.
Nas primeiras linhas são escritas as inclusões das bibliotecas necessárias, a definição da constante referente à quantidade de ***bytes*** que serão lidos e escritos por vez e o protótipo das funções.
#include <unistd.h>
#include <fcntl.h>
#define MAX_BUF 1024
void ft_cat(int fd);
void clear_buf(char *);
Em seguida tem o corpo da função main()
.
int main(int argc, char **argv)
{
int i;
int fd;
if (argc == 1)
{
fd = STDIN_FILENO;
ft_cat(fd);
}
else
{
i = 1;
while (i < argc)
{
fd = open(argv[i], O_RDONLY);
if (fd == -1)
{
write(2, "Error opening the file\n", 30);
return (1);
}
ft_cat(fd);
close(fd);
i++;
}
}
return (0);
}
Nela é declarada duas variáveis do tipo inteiro: i
e fd
.
Em seguida é verificado se foi informado arquivos para serem lidos ou não. Caso não tenham sido informados pelo usuário, o variável fd
recebe o FD referente ao stdin
, por meio da constante STDIN_FILENO
. Após, é chamada a ft_cat(fd)
passando o respectivo fd
.
Por outro lado, na situação do usuário tenha informado um ou mais arquivos; é atribuído o valor 1 para a variável i
e dado início ao loop, que será executado por enquanto i
for menor que o valor da variável argc
.
Para cada interação do loop: Um arquivo é aberto por meio da função open()
e o seu respectivo FD é salvo na variável fd
; é feita uma verificação de erro e, se estiver tudo ok, a função ft_cat()
é chamada tedndo o FD atual como parâmetro.
Por fim, as últimas linhas de execução do loop é: fechar o arquivo após a execução da função ft_cat()
e o incremento da variável i
para acessar o próximo arquivo, se houver.
Antes de passar para a explicação da função ft_cat()
; apenas anotar aqui sobre a função clear_buf()
. Que tem a tarefa de limpar a memória de alguma array recebida como parâmetro.
Assim, evita que haja restos de dados no buffer, principalmente quando estamos o utilizando no contexto do loop e leitura e escrita.
void clear_buf(char *buf)
{
int i;
i = 0;
while(i < MAX_BUF)
buf[i++] = '\0';
}
Agora, as notas para função ft_cat()
. Segue o código da respectiva.
void ft_cat(int fd)
{
int bytes_reads;
int bytes_writes;
char buf[MAX_BUF];
bytes_reads = 0;
bytes_writes = 0;
clear_buf(buf);
while ((bytes_reads = read(fd, buf, MAX_BUF - 1)) > 0)
{
buf[bytes_reads - 1] = '\0';
bytes_writes = write(1, buf, bytes_reads);
if (bytes_writes != bytes_reads)
{
write(2, "Error reading the file\n", 30);
return;
}
}
if (bytes_reads == -1)
{
write(2, "Error reading the file\n", 30);
return;
}
write(1, "\n\n", 2);
}
Ela recebe como parâmetro um inteiro que representa um FD de de onde os dados serão lidos. A escolha é pela opção, ao invés de receber o caminho de arquivo a ser lido, é pela possibilidade do FD referir tanto para arquivos, como também para outras fontes de da dados: tais como o stdin
.
De início são declaradas três variáveis: duas do tipo inteiro e uma cadeia de caráteres com tamanho definido pela constante MAX_BUF
. As variáveis bytes_reads
e bytes_writes
, como pelo nome dá para saber, serão onde armazenaremos os retornos das funções read()
e write()
. E a função buf[]
será onde salvaremos os dados lidos e a serem, posteriormente, escritos no stdout
.
Em seguida, são atribuídas as variáveis valores zero, com fim de evitar que haja dados remanescentes da memórias.
A atividade de leitura e escrita é feita no contexto de um *loop* while
*.*
O *loop* é executado por enquanto o retorno da chamada da função read()
for maior que zero. Na chamada da dessa função passamos, como parâmetro o FD atual da chamada da função ft_cat()
, o buf
onde os dados serão salvos e a quantidade de **bytes** que serão lidos por vez.
No corpo do *loop, adicionamos o caractere de terminação na última posição do buf
. Após é feita a chamada da função write()
para escrever no stdout
os dados presentes no buf
. Um verificação de erro é feita para checar se quantidade de *bytes** escritos é igual a quantidade a ser escrita. Caso haja alguma diferença, é disparado um erro.
Por fim, encerrada a execução do while
, outra verificação de erro é feita relativa à chamada da função read()
.
Conclusão
Bem, está foi uma implementação simples do utilitário cat
. Observando a proposta do desafio que apresentou restrições de recursos, o intuito com esta atividade é aprender a utilização das funções mais básicas de manipulação de entradas e saídas de dados na linguagem C.
Referências
https://twobithistory.org/2018/11/12/cat.html
https://en.wikipedia.org/wiki/Cat_(Unix)
https://history-computer.com/understanding-cat-command-in-linux-with-examples/
https://austinedger.medium.com/unix-commands-lets-build-cat-59b8a91b9708
Top comments (0)