Truncate a file
You are encouraged to solve this task according to the task description, using any language you may know.
Truncate a file to a specific length. This should be implemented as a routine that takes two parameters: the filename and the required file length (in bytes).
Truncation can be achieved using system or library calls intended for such a task, if such methods exist, or by creating a temporary file of a reduced size and renaming it, after first deleting the original file, if no other method is available. The file may contain non human readable binary data in an unspecified format, so the routine should be "binary safe", leaving the contents of the untruncated part of the file unchanged.
If the specified filename does not exist, or the provided length is not less than the current file length, then the routine should raise an appropriate error condition. On some systems, the provided file truncation facilities might not change the file or may extend the file, if the specified length is greater than the current length of the file. This task permits the use of such facilities. However, such behaviour should be noted, or optionally a warning message relating to an non change or increase in file size may be implemented.
Contents |
[edit] Ada
The following program is an implementation in Ada using system-independent tools from the standard library to read and write files, remove and rename them. It should work for on any system with byte-oriented file storage and uses Ada 2012 conditional expressions.
with Ada.Command_Line, Ada.Sequential_IO, Ada.Directories;
procedure Truncate_File is
type Byte is mod 256;
for Byte'Size use 8;
package Byte_IO is new Ada.Sequential_IO(Byte);
function Arg(N: Positive) return String renames Ada.Command_Line.Argument;
function Args return Natural renames Ada.Command_Line.Argument_Count;
begin
-- give help output if neccessary
if Args < 2 or else Args > 3 then
raise Program_Error
with "usage: truncate_file <filename> <length> [<temp_file>]";
end if;
-- now do the real work
declare
File_Name: String := Arg(1);
Temp_Name: String := (if Args = 2 then Arg(1) & ".tmp" else Arg(3));
-- an Ada 2012 conditional expression
File, Temp: Byte_IO.File_Type;
Count: Natural := Natural'Value(Arg(2));
Value: Byte;
begin
-- open files
Byte_IO.Open (File => File, Mode => Byte_IO.In_File, Name => File_Name);
Byte_IO.Create(File => Temp, Mode => Byte_IO.Out_File, Name => Temp_Name);
-- copy the required bytes (but at most as much as File has) from File to Temp
while (not Byte_IO.End_Of_File(File)) and Count > 0 loop
Byte_IO.Read (File, Value);
Byte_IO.Write(Temp, Value);
Count := Count - 1;
end loop;
-- close files
Byte_IO.Close(Temp);
Byte_IO.Close(File);
if Count = 0 then -- File was at least Count bytes long
-- remove File and rename Temp to File
Ada.Directories.Delete_File(Name => File_Name);
Ada.Directories.Rename(Old_Name => Temp_Name, New_Name => File_Name);
else -- File was too short
-- remove Temp and leave File as it is, output error
Ada.Directories.Delete_File(Name => Temp_Name);
raise Program_Error
with "Size of """ & File_Name & """ less than " & Arg(2);
end if;
end;
end Truncate_File;
[edit] BASIC
This is fairly generic MS BASIC. As such, it actually works in a wide variety of compilers and interpreters -- certainly too many to list here, but the list includes non-.Net Visual Basic, FreeBASIC, QB64, etc. With a user-provided implementation of DIR$, it even works under QBasic.
SUB truncateFile (file AS STRING, length AS LONG)
IF LEN(DIR$(file)) THEN
DIM f AS LONG, c AS STRING
f = FREEFILE
OPEN file FOR BINARY AS f
IF length > LOF(f) THEN
CLOSE f
ERROR 62 'Input past end of file
ELSE
c = SPACE$(length)
GET #f, 1, c
CLOSE f
OPEN file FOR OUTPUT AS f
PRINT #f, c;
CLOSE f
END IF
ELSE
ERROR 53
END IF
END SUB
See also: Liberty BASIC, PowerBASIC, PureBasic, ZX Spectrum Basic.
[edit] BBC BASIC
This will extend the file if the specified length is greater than the existing length. A test to prevent that could easily be added.
DEF PROCtruncate(file$, size%)
LOCAL file%
file% = OPENUP(file$)
IF file%=0 ERROR 100, "Could not open file"
EXT#file% = size%
CLOSE #file%
ENDPROC
[edit] C
[edit] Windows
Windows uses SetEndOfFile() to change the length of a file. This program can truncate or extend a file. It can detect and print errors.
- If the file does not exist: "The system cannot find the file specified."
- If the length is negative: "An attempt was made to move the file pointer before the beginning of the file."
- If the length is too large: "There is not enough space on the disk."
#include <windows.h>
#include <stdio.h>
#include <wchar.h>
/* Print "message: last Win32 error" to stderr. */
void
oops(const wchar_t *message)
{
wchar_t *buf;
DWORD error;
buf = NULL;
error = GetLastError();
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (wchar_t *)&buf, 0, NULL);
if (buf) {
fwprintf(stderr, L"%ls: %ls", message, buf);
LocalFree(buf);
} else {
/* FormatMessageW failed. */
fwprintf(stderr, L"%ls: unknown error 0x%x\n",
message, error);
}
}
int
dotruncate(wchar_t *fn, LARGE_INTEGER fp)
{
HANDLE fh;
fh = CreateFileW(fn, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (fh == INVALID_HANDLE_VALUE) {
oops(fn);
return 1;
}
if (SetFilePointerEx(fh, fp, NULL, FILE_BEGIN) == 0 ||
SetEndOfFile(fh) == 0) {
oops(fn);
CloseHandle(fh);
return 1;
}
CloseHandle(fh);
return 0;
}
/*
* Truncate or extend a file to the given length.
*/
int
main()
{
LARGE_INTEGER fp;
int argc;
wchar_t **argv, *fn, junk[2];
/* MinGW never provides wmain(argc, argv). */
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (argv == NULL) {
oops(L"CommandLineToArgvW");
return 1;
}
if (argc != 3) {
fwprintf(stderr, L"usage: %ls filename length\n", argv[0]);
return 1;
}
fn = argv[1];
/* fp = argv[2] converted to a LARGE_INTEGER. */
if (swscanf(argv[2], L"%lld%1ls", &fp.QuadPart, &junk) != 1) {
fwprintf(stderr, L"%ls: not a number\n", argv[2]);
return 1;
}
return dotruncate(fn, fp);
}
[edit] POSIX
#include <unistd.h>
#include <sys/types.h>
...
truncate(filename, length);
ftruncate(fd, length);
...
Both functions have length argument of off_t type. There are about a million possible errors, of interest to this task are EFBIG: size too large; EINVAL: size is negative or too large; EIO: IO error; EACCESS (for truncate): either can't see or can't write to the file; EBADF or EINVAL (for ftruncate): file descriptor is not a file descriptor, or not opened for writing.
When specifying a new file size larger than current value, the file will be extended and padded with null bytes.
[edit] C#
using System;
using System.IO;
namespace TruncateFile
{
internal class Program
{
private static void Main(string[] args)
{
TruncateFile(args[0], long.Parse(args[1]));
}
private static void TruncateFile(string path, long length)
{
if (!File.Exists(path))
throw new ArgumentException("No file found at specified path.", "path");
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Write))
{
if (fileStream.Length < length)
throw new ArgumentOutOfRangeException("length",
"The specified length is greater than that of the file.");
fileStream.SetLength(length);
}
}
}
}
[edit] D
import std.file, std.exception;
void truncateFile(in string name, in size_t newSize) {
if (!exists(name) || !isFile(name))
throw new Exception("File not found.");
auto size = getSize(name);
if (size <= newSize)
throw new Exception(
"New size must be smaller than original size.");
auto content = cast(ubyte[])read(name, newSize);
if (content.length != newSize)
throw new Exception("Reading file failed.");
write(name, content);
enforce(getSize(name) == newSize);
}
void main() {
truncateFile("truncate_test.txt", 0);
}
[edit] Go
Go has the required function in the standard library. The implementation calls operating system specific functions and returns whatever errors the operating system reports.
import (
"fmt"
"os"
)
if err := os.Truncate("filename", newSize); err != nil {
fmt.Println(err)
}
Package os also has a separate function that operates on an open file.
[edit] Haskell
This can be achieved by using the setFileSize function in System.PosixCompat.Files:
setFileSize :: FilePath -> FileOffset -> IO ()
-- Truncates the file down to the specified length.
-- If the file was larger than the given length
-- before this operation was performed the extra is lost.
-- Note: calls truncate.
[edit] Icon and Unicon
Unicon provides the built-in function truncate which can be used to truncate a file. The following line of code truncates filename to newsizeinbytes. The file is opened for both read and write in untranslated mode.
truncate(f := open(filename, "bu"), newsizeinbytes) & close(f)
Note: The Unicon book incorrectly indicates that truncate doesn't work on Windows.
[edit] J
Solution:
require 'files' NB. needed for versions prior to J7
ftruncate=: ] fwrite~ ] fread@; 0 , [
Usage:
(1000$ 'abcdefg') fwrite 'text.txt' NB. create test file
567 ftruncate 'test.txt' NB. truncate test file
567
fsize 'test.txt' NB. check new file size
567
[edit] Java
The built-in function for truncating a file in Java will leave the file unchanged if the specified size is larger than the file. This version expects the source file name and the new size as command line arguments (in that order).
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class TruncFile {
public static void main(String[] args) throws IOException{
if(args.length < 2){
System.out.println("Usage: java TruncFile fileName newSize");
return;
}
//turn on "append" so it doesn't clear the file
FileChannel outChan = new FileOutputStream(args[0], true).getChannel();
long newSize = Long.parseLong(args[1]);
outChan.truncate(newSize);
outChan.close();
}
}
[edit] Liberty BASIC
Note Liberty BASIC provides a way to exit a function.
It is also possible to call API functions to achieve this task.
dim info$( 50, 50) ' NB pre-dimension before calling file-exists
' needed for file-exists function
open "test.dat" for output as #1 'create file
for i = 1 to 10000
#1 chr$( int( 256 *rnd( 1)));
next
close #1
call truncateFile, "test.dat", 5000
wait
sub truncateFile fn$, length
if fileExists( DefaultDir$, fn$) =0 then notice "No such file": exit sub
open fn$ for input as #i
file$ =input$( #i, lof( #i))
if len( file$) <length then notice "Too short": close #i: exit sub
file$ =left$( file$, length)
close #i
open "test.dat" for output as #o
#o file$
close #o
end sub
function fileExists( path$, filename$)
files path$, filename$, info$()
fileExists =val( info$( 0, 0)) 'non zero is true
end function
end
[edit] Mathematica
Truncate[file_, n_] := Module[{filename = file, nbbytes = n, temp},
temp = $TemporaryPrefix <> filename;
BinaryWrite[temp, BinaryReadList[filename, "Byte", nbbytes]];
Close[temp]; DeleteFile[filename]; RenameFile[temp, filename];
]
[edit] OCaml
The Unix module provided with the standard distribution provides a function truncate:
val truncate : string -> int -> unit
(** Truncates the named file to the given size. *)
There is also a function ftruncate that does the equivalent operation but with a file descriptor instead of a file name:
val ftruncate : file_descr -> int -> unit
(** Truncates the file corresponding to the given descriptor to the given size. *)
[edit] Pascal
Program FileTruncate;
uses
SysUtils;
var
myfile: file of byte;
filename: string;
position: integer;
begin
write('File for truncation: ');
readln(filename);
if not FileExists(filename) then
begin
writeln('Error: File does not exist.');
exit;
end;
write('Truncate position: ');
readln(position);
Assign(myfile, filename);
Reset(myfile);
if FileSize(myfile) < position then
begin
writeln('Warning: The file "', filename, '" is too short. No need to truncate at position ', position);
Close(myfile);
exit;
end;
Seek(myfile, position);
Truncate(myfile);
Close(myfile);
writeln('File "', filename, '" truncated at position ', position, '.');
end.
Output:
File for truncation: test Truncate position: 3 File "test" truncated at position 3.
[edit] Perl
# Open a file for writing, and truncate it to 1234 bytes.
open FOO, ">>file" or die;
truncate(FOO, 1234);
close FOO;
# Truncate a file to 567 bytes.
truncate("file", 567);
[edit] Perl 6
use NativeCall;
sub truncate(Str, Int --> int) is native {*}
sub MAIN (Str $file, Int $to) {
given $file.IO {
.e or die "$file doesn't exist";
.w or die "$file isn't writable";
.s >= $to or die "$file is not big enough to truncate";
}
truncate($file, $to) == 0 or die "Truncation was unsuccessful";
}
[edit] PicoLisp
On the 64-bit version, we can call the native runtime library:
(de truncate (File Len)
(native "@" "truncate" 'I File Len) )
Otherwise (on all versions), we call the external truncate command:
(de truncate (File Len)
(call "truncate" "-s" Len File) )
[edit] PL/I
/* Parameters to be read in by the program are: */
/* 1. the name of the file to be truncated, and */
/* 2. the size of file after truncation. */
truncate_file: procedure options (main); /* 12 July 2012 */
declare (i, n) fixed binary (31);
declare filename character(50) varying;
declare in file record input, out file record output;
put ('What is the name of the file to be truncated?');
get edit (filename) (L);
put ('What is the length of file to be retained?');
get (n);
begin;
declare c(n) character (1), ch character (1);
open file (in) title ('/' || filename || ',type(fixed),recsize(1)' )
input;
do i = 1 to n; read file (in) into (ch); c(i) = ch; end;
close file (in);
open file (out) title ('/' || filename || ',append(n),type(fixed),
recsize(' || trim(n) || ')' );
write file (out) from (c);
close file (out);
end;
end truncate_file;
[edit] PowerBASIC
While PowerBASIC can use the QuickBASIC version of truncateFile, PB provides an easier way to do this, via the SETEOF function -- but since SETEOF will extend a file if it is not as long as specified, we still need to wrap it in a SUB to meet this task's specifications.
SUB truncateFile (file AS STRING, length AS DWORD)
IF LEN(DIR$(file)) THEN
DIM f AS LONG
f = FREEFILE
OPEN file FOR BINARY AS f
IF length > LOF(f) THEN
CLOSE f
ERROR 62 'Input past end
ELSE
SEEK f, length + 1
SETEOF f
CLOSE f
END IF
ELSE
ERROR 53 'File not found
END IF
END SUB
[edit] PureBasic
PureBasic has the internal function TruncateFile that cuts the file at the current file position and discards all data that follows.
Procedure SetFileSize(File$, length.q)
Protected fh, pos, i
If FileSize(File$) < length
Debug "File to small, is a directory or does not exist."
ProcedureReturn #False
Else
fh = OpenFile(#PB_Any, File$)
FileSeek(fh, length)
TruncateFile(fh)
CloseFile(fh)
EndIf
ProcedureReturn #True
EndProcedure
[edit] Python
def truncate_file(fname, size):
"Open a file for writing, and truncate it to size bytes."
with open(fname, "ab") as f:
f.truncate(size)
[edit] Racket
Racket has a file-truncate function that expects an open port and truncate its associated file. Note that it can also extend the file, and the code below prints a warning in that case.
#lang racket
(define (truncate file size)
(unless (file-exists? file) (error 'truncat "missing file: ~a" file))
(when (> size (file-size file)) (printf "Warning: extending file size.\n"))
(call-with-output-file* file #:exists 'update
(λ(o) (file-truncate o size))))
[edit] Ruby
This only works with some platforms. If truncation is not available, then Ruby raises NotImplementedError.
# Open a file for writing, and truncate it to 1234 bytes.
File.open("file", "ab") { |f| f.truncate(1234) }
# Truncate a file to 567 bytes.
File.truncate("file", 567)
[edit] Tcl
package require Tcl 8.5
set f [open "file" r+]; # Truncation is done on channels
chan truncate $f 1234; # Truncate at a particular length (in bytes)
close $f
[edit] UNIX Shell
The dd(1) command can truncate a file. Because dd(1) would create the file, this example runs ls(1). If the file does not exist, then ls(1) prints an error. If the file exists, then dd(1) truncates the file or prints an error. Unix can extend a file, so there is no error if the length increases.
# Truncate a file named "myfile" to 1440 kilobytes.
ls myfile >/dev/null &&
dd if=/dev/null of=myfile bs=1 seek=1440k
Some systems have a truncate(1) command (FreeBSD truncate(1), GNU truncate(1)).
# Truncate a file named "myfile" to 1440 kilobytes.
truncate -s 1440k myfile
[edit] ZX Spectrum Basic
We can truncate files that were saved as binary. We don't know the length of the original file, so if the provided length is longer, then the file will be extended.
10 CLEAR 29999
20 INPUT "Which file do you want to truncate?";f$
30 PRINT "Start tape to load file to truncate."
40 LOAD f$ CODE 30000
50 "Input how many bytes do you want to keep?";n
60 PRINT "Please rewind the tape and press record."
70 SAVE f$ CODE 30000,n
80 STOP