1

I want to create a text file with mulitple lines using system calls in C and populate it with the text provided as command line arguments.

This is what I wrote:

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_SZ 1024

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Invalid Number of arguments\n");
        printf("USAGE: ./a.out file_name \"msg\"\n");
    } else {
        int fd_creat, fd_open, fd_write;
        char file_name[MAX_SZ];
        char *msg = (char *)malloc(strlen(argv[2]) * sizeof(char));
        strcpy(file_name, argv[1]);
        fd_creat = creat(file_name, 0777);
        if (fd_creat < 2) {
            printf("ERROR: File could not be created\n");
        } else {
            fd_open = open(file_name, O_WRONLY);
            strcpy(msg, argv[2]);
            fd_write = write(fd_open, msg, strlen(msg));
            close(fd_open);
        }
    }
    return 0;
}

If I execute this program as:

./a.out test.txt "Foo\nBar"

It writes the whole thing into test.txt as it is. Basically, I want 'Foo' and 'Bar' in their separate lines.

Apurba
  • 13
  • 2
  • You may want to use `PATH_MAX` for paths. That being said, why are you copying the arg to a buffer but not changing it? How is this better than just using `argv[1]` directly? – tadman Nov 14 '20 at 02:21
  • You do this again with `argv[2]`. Maybe this is the result of a mistaken assumption that you need to copy `argv` values before using them, but this is not the case. – tadman Nov 14 '20 at 02:23
  • It's also not necessary to call `creat`. Just open the file in write mode and start writing. In C you'll tend to want to use the `FILE*`-based functions like `fopen` instead of the low-level `open` function. – tadman Nov 14 '20 at 02:23
  • 2
    You need one more element for teminating null-character for `msg`. – MikeCAT Nov 14 '20 at 02:23
  • Yes, I know argv values can be used directly, I used separate variables for making the code a bit more readable – Apurba Nov 14 '20 at 02:26
  • If you want 'Foo' and 'Bar' on separate lines, it would be easiest to have the program accept them as separate command line arguments. In other words the command line would be `./a.out test.txt Foo Bar` – user3386109 Nov 14 '20 at 02:26
  • 1
    To make the code readable without doing unnecessary memory allocation and copying, just do `char *file_name = argv[1];` and `char *msg = argv[2];` – user3386109 Nov 14 '20 at 02:29
  • The separate variables make the code far less readable and as Mike pointed out, it also created a bunch of bugs. – tadman Nov 14 '20 at 02:33
  • I agree, but is there any way I could get the desired output without using separate command line arguments? – Apurba Nov 14 '20 at 02:36

1 Answers1

2

There's two problems here:

  • The way you're handling arguments and failing to allocate enough memory for the data involved,
  • Interpreting escape sequences like \n correctly since the shell will give them to you as-is, raw.
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

// This moves overlapping strings from src -> dest but only
// if dest is before src
void cc_str_drag(char* dest, char* src) {
    while (*dest) {
        *dest = *src;
        ++dest;
        ++src;
    }
}

// This interprets the \n sequence and can be extended to handle others, like
// \t, \\, or even \g.
void cc_interpret(char* str) {
    for (;*str; ++str) {
        // If this is a sequence start...
        if (*str == '\\') {
            // ...find out which one...
            switch (str[1]) {
                case 'n':
                    // Shift back...
                    cc_str_drag(str, &str[1]);
                    // ...and replace it.
                    *str = '\n';
                    break;
            }
        }
    }
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("Invalid Number of arguments\n");

        // Remember argv[0] is the name of the program
        printf("USAGE: %s file_name \"msg\"\n", argv[0]);
        return -1;
    }

    // Since it's not the 1970s, use fopen() and FILE*
    FILE* output = fopen(argv[1], "w");

    if (!output) {
        printf("ERROR: File could not be created\n");
        return -2;
    }

    // Copying here to avoid tampering with argv
    char* str = strdup(argv[2]);

    // Replace any escape sequences
    cc_interpret(str);

    // Then just dump it directly into the file
    fwrite(str, 1, strlen(str), output);

    fclose(output);

    return 0;
}

Note the tools used here:

  • strdup is a way quicker method of copying a C string than malloc(strlen(s)) and then copying it. That's asking for dreaded off-by-one errors.
  • FILE* performs much better because it's buffered. open() is used for low-level operations that can't be buffered. Know when to use which tool.
  • Don't be afraid to write functions that manipulate string contents. C strings are really important to understand, not fear.
tadman
  • 208,517
  • 23
  • 234
  • 262
  • 1
    There was one small bug in your code though, For input: "Foo\nBar", your code gave "Foo" and "arr" in separate lines, I modified the "strcpy(str, &str[1]);" with "strcpy(&str[1], &str[2]);" to get the desired output ("Foo" and "Bar" on separate lines). Thank you for your help! – Apurba Nov 14 '20 at 05:12
  • I tested this as-is and it worked for me, producing the correct output for that as well, so maybe something's slightly different in your version. Glad it worked though! It's possible that use of `strcpy()` to move things around in a string like that isn't valid, I wrote my own string transposer but removed it at the last second thinking `strcpy` might do the trick. – tadman Nov 14 '20 at 07:49
  • The [documentation](https://en.cppreference.com/w/c/string/byte/strcpy) states "The behavior is undefined if the strings overlap." which is what I was worried about. – tadman Nov 14 '20 at 07:52
  • I've put the string moving function back in there. – tadman Nov 14 '20 at 07:55