Search code examples
fileunixtcl

Inserting into a text file using tcl


My tcl script is designed to update a table in CSV file inserting a new row under its header. Let's say there are only 2 columns: "Date" and "Description". To do that, I first open the CSV file in a read mode, store its whole content as a variable and close that file. Then I open the file again in a write mode and overwrite its content by my variable (which I update according my requirements). The simplified code is:

proc updateCSV {myDescription myDate myPath} {
    set newContent "Date,Description\n$myDate,$myDescription";

    #Stage A: Process the target file in a read mode:
    set targetFile [open $myPath "r"];
    #Store the existing content and remove the first line (header-line)
    set existingContent [read $targetFile];
    set newlinePos [string first "\n" $existingContent];
    set oldContent [string range $existingContent $newlinePos+1 end];
    close $targetFile;

    #Stage B: Process the target file in a write mode:
    set targetFile [open $myPath "w"];
    #Add the header-line and the new content-line, overwrite the whole existing content:
    puts $targetFile $newContent;   
    #Append the previous content:
    seek $targetFile 0 end;
    puts -nonewline $targetFile $oldContent;
    close $targetFile;
}

What bothers me is that the above solution seems too clumsy. Is there a way to do the same stuff by: a) processing the target file only once; b) avoid overwriting its content but rather updating it directly?


Solution

  • Rather than reading the entire file, modifying it, and then overwriting the content, you can use a different approach that involves reading the file line by line, making modifications as needed, and then writing those changes to a new file. Here's an example using Tcl to update the CSV file directly without overwriting its content:

    proc updateCSV {myDescription myDate myPath} {
        set tempFile [file tempfile updatedCSV];
        set header "Date,Description";
    
        # Open the input file for reading
        set inputFile [open $myPath "r"];
        set outputFile [open $tempFile "w"];
    
        # Read and write the header to the output file
        puts $outputFile $header;
    
        # Flag to indicate whether the header was found
        set headerFound 0;
    
        # Process each line of the input file
        while {[gets $inputFile line] != -1} {
            if {!$headerFound && [string equal $line $header]} {
                # Skip the existing header in the input file
                set headerFound 1;
                continue;
            }
            # Write the line to the output file
            puts $outputFile $line;
        }
    
        # Append the new row to the end of the file
        puts $outputFile "$myDate,$myDescription";
    
        # Close both files
        close $inputFile;
        close $outputFile;
    
        # Replace the original file with the updated file
        file rename -force $tempFile $myPath;
    }
    

    This script reads the input CSV file line by line. It skips the existing header and writes all other lines to a temporary file (updatedCSV). After processing all lines, it appends the new row ($myDate,$myDescription) to the end of the file and then replaces the original file with the updated one.

    This way, you're not required to store the entire content in a variable, and it operates more efficiently by directly modifying the file as needed without overwriting its content entirely.