Remove the last char of a text file.

Posted by Dominik Loewenstein on 22-Jun-2017 01:07

is it possible to remove the last char of a text file ?
I am really struggling with this problem. Something like  sending a backspace "chr(08)" also "~b" won't help.
I dont want to read the whole file again and write a new one without the last char.

Posted by Peter Van Dyck on 22-Jun-2017 03:34

In my opinion it's not possible to perform this action without overwriting the file.

You're asking for a function that asks the file system to cut off the last character of a file. That operating-system command doesn't exist, I'm quite sure.

What you'd have to do is read the file, remove the last character, then overwrite it.

- copy-lob file to variable

- update variable

- copy-lob variable to file

All Replies

Posted by Matt Gilarde on 22-Jun-2017 03:00

I don't think there's any way to do it in ABL without making a copy of the file. You will have to use operating system functions (via DLL or shared object calls) or execute a shell script or batch file. What operating system do you want to do this on? 

Posted by Dominik Loewenstein on 22-Jun-2017 03:14

I use windows, "~n" for example worked fine but erase a char want do the right job

Posted by Peter Van Dyck on 22-Jun-2017 03:34

In my opinion it's not possible to perform this action without overwriting the file.

You're asking for a function that asks the file system to cut off the last character of a file. That operating-system command doesn't exist, I'm quite sure.

What you'd have to do is read the file, remove the last character, then overwrite it.

- copy-lob file to variable

- update variable

- copy-lob variable to file

Posted by Brian K. Maher on 22-Jun-2017 04:04

Hi Dominik,
 
How about this?  Technically it does read the file but it works...
 
DEFINE VARIABLE vSize AS INTEGER  NO-UNDO.
 
FILE-INFO:FILE-NAME = "testfile".
ASSIGN vSize = FILE-INFO:FILE-SIZE.
COPY-LOB FROM FILE "testfile" FOR (vSize - 1) TO FILE "testfile.clipped".
 
You may also be able to do this using the SEEK statement.
 
Brian

Posted by Matt Gilarde on 22-Jun-2017 05:47

On Windows you can do this programmatically with the SetEndOfFile Win32 API function. To use this function you would first open the file using CreateFile, seek to the position where you want to truncate the file using SetFilePointer, call SetEndOfFile to truncate the file, and then close the file with CloseHandle. You could call these functions using the external program interface (DLL calls) or create a DLL which does all of the work so you only have to make one DLL call from ABL.

There may also be file system commands which can perform this action. Linux has a truncate command. There may be something similar on Windows.

Posted by Peter Van Dyck on 22-Jun-2017 05:48

@ Brian:

I think the intention was to write to the same file (overwrite it), so combining your solution with mine would become:

define variable vFileContents as longchar no-undo.

copy-lob file "hello.txt" to vFileContents.

copy-lob vTest for length(vFileContents) - 1 to file "hello.txt".

@ Matt:

Didn't know that, I guess it just depends on how much you want to avoid having to read the file in memory.

Posted by Brian K. Maher on 22-Jun-2017 05:54

Hi Peter,
 
You may need to use the optional parameter to LENGTH to tell it to count raw bytes instead of logical characters --> length(vFileontents,”RAW”) – 1
 
Brian

Posted by Brian K. Maher on 22-Jun-2017 06:00

If you are using a version of Windows which has the fsutil command you can do this using the following....
 
fsutil file seteof testfile 15 (or whatever)
 
so code to do this would be...
 
file-info:file-name = “whatever”.
os-command silent value(“fsutil file seteof “ + file-info:full-pathname + “ “ + string(file-info:file-size – 1).
 
The one potential issue is that fsutil needs to run as administrator.  I tested it via a normal command prompt against a file I created and got an access error but running the command with a ‘run as administrator’ command prompt worked.
 
Cheers, Brian

Posted by Peter Van Dyck on 22-Jun-2017 06:01

You're right. Depends on whether it's a text-file or not. As Dominic was talking about "backspace", I was assuming it's a text-file.

Posted by Matt Gilarde on 22-Jun-2017 06:13

If you do it by reading the file and writing out the contents without the last character, you should write to a different file and then rename the new file to the original file name. Otherwise you risk losing the original file if something goes wrong.

Read contents of original file into CLOB

Write contents of file minus one character to tempfile

Delete original file

Rename tempfile to original file name

Posted by Dominik Loewenstein on 22-Jun-2017 06:17

Thx for your replies, there are many good suggestions. I hoped i only dont know the right command. With "APPEND" i come straight to the last line, but cant remove something, that's a pity.

At the moment my file is not so big, but with bigger files it would be horrible to copy paste the whole file only to remove one char

Posted by Matt Gilarde on 22-Jun-2017 07:23

Here's code that uses SetEndOfFile to chop one byte off the end of a file.

&GLOBAL-DEFINE GENERIC_WRITE 0x40000000
&GLOBAL-DEFINE OPEN_EXISTING 3
&GLOBAL-DEFINE FILE_ATTRIBUTE_NORMAL 0x80
&GLOBAL-DEFINE FILE_END 2

PROCEDURE CreateFileA EXTERNAL "kernel32":
    DEFINE INPUT PARAMETER lpFileName AS CHARACTER.
    DEFINE INPUT PARAMETER dwDesiredAccess AS LONG.
    DEFINE INPUT PARAMETER dwShareMode AS LONG.
    DEFINE INPUT PARAMETER lpSecurityAttributes AS LONG.
    DEFINE INPUT PARAMETER dwCreationDisposition AS LONG.
    DEFINE INPUT PARAMETER dwFlagsAndAttributes AS LONG.
    DEFINE INPUT PARAMETER hTemplateFile AS LONG.
    DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE CloseHandle EXTERNAL "kernel32" :
  DEFINE INPUT  PARAMETER hObject     AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE SetEndOfFile EXTERNAL "kernel32" :
  DEFINE INPUT  PARAMETER hObject     AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

PROCEDURE SetFilePointer EXTERNAL "kernel32" :
  DEFINE INPUT  PARAMETER hObject     AS LONG.
  DEFINE INPUT  PARAMETER distanceToMove AS LONG.
&IF {&PROCESS-ARCHITECTURE} = 64 &THEN
  DEFINE INPUT  PARAMETER distanceToMoveHigh AS INT64.
&ELSE
  DEFINE INPUT  PARAMETER distanceToMoveHigh AS LONG.
&ENDIF
  DEFINE INPUT  PARAMETER moveMethd   AS LONG.
  DEFINE RETURN PARAMETER ReturnValue AS LONG.
END PROCEDURE.

DEFINE VARIABLE lpSecurityAtt AS INTEGER NO-UNDO.
DEFINE VARIABLE hObject AS INTEGER NO-UNDO.
DEFINE VARIABLE nReturn AS INTEGER NO-UNDO.

RUN CreateFileA (INPUT "file.txt", 
                 INPUT {&GENERIC_WRITE}, 
                 0, /* no sharing */
                 lpSecurityAtt, 
                 {&OPEN_EXISTING},
                 {&FILE_ATTRIBUTE_NORMAL},
                 0,
                 OUTPUT hObject).
                 
IF hObject <> -1 THEN
DO:
    RUN SetFilePointer(hObject, -1, 0, {&FILE_END}, OUTPUT nReturn).
    RUN SetEndOfFile(hObject, OUTPUT nReturn).
    RUN CloseHandle(hObject, OUTPUT nReturn).
END.

Posted by MTBOO on 22-Jun-2017 07:53

Depending on your environment you could look at System.IO.FileStream e.g.

USING System.IO.*.

DEF VAR ofs AS System.IO.FileStream NO-UNDO.

DEF VAR cFile AS CHAR NO-UNDO.

cFile = "c:\temp\myfile.txt".

ofs = NEW FileStream(cFile, FileMode:Open).

IF ofs:CanSeek AND ofs:CanWrite THEN

DO:

   ofs:SetLength(ofs:LENGTH - 1).

   ofs:Close().

END.

Posted by gus bjorklund on 23-Jun-2017 05:53

on Linux and UNIX you can call the C function ftruncate () (or truncate () if file not open) to reduce the length of the file by 1 byte.

that might accomplish what you want.

This thread is closed