os_lib.pl -- Operating system interaction predicates.

This library collects a number of predicates useful for OS interactions. The emphasis here is on operations on files and directories rather than on calling OS commands. Unlike the system predicates of SWI/Yap here we adhere to the <lib>_ convention prefix that allows for more succinct predicate names. The assumption is that by using prefix "os", there will be a main argument that is an OS entity, so the predicate name does not have to explicitly refer to all the arguments. For instance


4 ways to name OS objects
and particulalry output variable casting via variable decoration
os_exists(Os, Opts)
works on files and dirs by default, and can be specialised via Opts
os_postfix(Psfx, Os, Posted)
os_postfix/3 add a bit on a OS name to produce a new one
os_ext(Ext, Stem, Os)
os_ext/3 is a renamed file_name_extension/3 with few extra bits
os_unique(Token, Os, Opts)
constructs unique filenames either based ondate (and possible time stamp) or on versioning
os_dir_stem_ext(Dir, Stem, Ext, Os)
os_dir_stem_ext/4 construct and de-construct OS names from/to its main parts
os_dir_stem_ext(Os, Opts)
os_mill(File, Goal, Milled, Opts)
os_mill/4 allows construction of evolving pipelines
os_file/1 backtrack over all files in current directory

In addition, the library is polymorphic in naming OS objects by supporting 4 different os term structures:

Currently the emphasis is on file name manipulations but command (eg copy_file) are likely to be included in new versions. Main reason why they are not yes, is that we use pack(by_unix).


To install

?- pack_install( os_lib ).

to load

?- use_module( library(os) ).


?- use_module( library(os_lib) ).


The library attempts to keep a consistent set of options that are on occasions funnelled through to either other interface or commonly used private predicates.

Common options:

directory for input and output
directory for input (overrides Dir)
directory for output (overrides Dir)
extension for file
stem to be used for constructing os names
apply operation recursive to sub directories

Variable name conventions

An os entity



\ Os
casts to /-terms
+ Os
casts to atoms
casts to strings
casts to alias (input must be an alias to start with)



The library predicates can be split to 4 groups.

  1. Predicates for manipulating and constructing OS entity names
  2. Commands
  3. Logical
  4. Helpers


- nicos angelopoulos
- 0.0.1 2015/4/30
- 0.0.2 2015/4/30 added module documentation
- 0.0.3 2015/12/10 redone the typing and added better alias support, started custom errors
- 0.1.0 2016/2/24 first publisc release
- 0.6.0 2017/3/10 works with pack(lib)
- 1.0.0 2018/3/18
- 1.2.0 2018/8/5 added os_files/1,2 and os_dirs/1,2 (with options) and removed os_dir_files/2 and os_dir_dirs/2.
- 1.3 2018/10/1 cleaner error handling via throw, new opt dots(D), os_cast/3 arguments switch
- 1.4 2019/4/22 option sub(Sub); cp_rec.pl script; list of postfixes, etc
- 1.5 2019/4/22 os_path/2, fixes and new options to os_mill/4 ; os_exists/2 (return type) & os_sel/4
- 1.6 2022/6/14 fixed: os_exist( file, type(link) ), added option read_link(RLnk) to os_file/2
- 1.7 2024/2/7 fixes in: os_unique/3 and os_ext/4
See also
- http://www.stoics.org.uk/~nicos/sware/os
- http://www.stoics.org.uk/~nicos/sware/os/html/os_lib.html
- doc/Releases.txt
To be done
- os_pwd/1 (working_directory + casting)
- use os_path/3 as a template to convert all lib predicates to castable outputs.
- there might a bit of dead code around
 os_dir_stem_ext(-Os, +Opts)
Create an os object (Os) from bits in Opts.


extension for File
directory for File (if ODIr is not given)
when true, do not use extension for Os
preferred option when constructing Os
stem for Os
or dir for directory (type only matters for handling extension)
?- os_dir_stem_ext( Os, [stem(file),ext(csv)] ).
Os = file.csv.

?- os_dir_stem_ext( Os, [stem(some_dir),ext(csv),type(dir)] ).
Os = some_dir.
- nicos angelopoulos
- 0.1 2016/6/24
- 0.2 2017/7/6, added type() and dir_surpress_ext()
See also
- module docs (how to cross-ref?)
 os_dir_stem_ext(+Dir, +Stem, +Ext, -File)
os_dir_stem_ext(-Dir, -Stem, -Ext, +File)
Construct and deconstruct filename File, from and to components: directory Dir, stem Stem and extension Ext.
 ?- os_dir_stem_ext( data/what, file, csv, File ).
 File = data/what/file.csv.
 ?- os_dir_stem_ext( 'data/what', file, csv, File ).
 File = 'data/what/file.csv'.
 ?- os_dir_stem_ext( library(os), file, csv, File ).
 File = library('os/file.csv').

 ?- os_dir_stem_ext( Dir, Stem, Ext, library('os/file.csv') ).
 Dir = '/usr/local/users/nicos/local/git/lib/swipl-7.3.16/library/os',
 Stem = file,
 Ext = csv.
 ?- os_dir_stem_ext( dir, stem, ext, /Os ).

 ?- os_dir_stem_ext( "data/what", file, csv, File ).
 File = "data/what/file.csv".

 ?- os_dir_stem_ext( "data/what", file, csv, +File ).
 File = 'data/what/file.csv'.

Note: there is no quarantee the path to the file or the file itself exist.

Also File, might a relative or absolute reference depending on Dir's disposition.

- nicos angelopoulos
- 0.3 2016/2/7
See also
- was dir_stem_ext_file/4
 os_stem(-Stem, +File, +Opts)
os_stem(+Stem, -File, +Opts)
os_stem(-Stem, -File, +Opts)
 os_stem(-Stem, +Opts)
This predicate is meant to be used for generating the second argument (File). See os_path/3 if you looking for a more general relation between Stem, File and Dir. When Stem is a variable, Psfx should be given in Opts below. In addition, hen os_stem/2 is used, From should be given.


default dir only used if ODir is missing, or to return Dir if File is given
odir(ODir = )
output directory
ext(Ext = )
extension for File in relation to Stem
get stem from From (also need to supply postfix())
when both Stem and File are variables and From is given, setting this to false will use the parent of From as the destination directory (else, either Odir or Dir is used). The default is true except when either Dir or ODir are given in Opts
added to From,
?- os_stem( abc, File, dir(sub) ).
File = 'sub/abc'.

?- os_stem( abc, File, [dir(sub),ext(csv)] ).
File = 'sub/abc.csv'.

?- os_stem( Var, File, [from(abc/foo.bar),postfix(ps)] ).
Var = File, File = abc/foo_ps.

?- os_stem( Stem, File, [from(abc/foo.bar),postfix(ps),dir(doc)] ).
Stem = abc/foo_ps,
File = doc/foo_ps.

?- os_stem( Stem, File, [from(abc/foo.bar),postfix(ps),dir(doc),use_from_dir(true)] ).
Stem = File, File = abc/foo_ps.

?- os_stem( Stem, +(File), [from(abc/foo.bar),postfix(ps),dir(doc)] ).
Stem = abc/foo_ps,
File = 'doc/foo_ps'.

?- os_stem( "abc", File, dir(sub) ).
File = 'sub/abc'.

?- os_stem( "abc", File, dir("sub") ).
File = "sub/abc".

?- os_stem( abc, File, dir("sub") ).
 os_ext(?Ext, +File)
 os_ext(+Ext, +Stem, -File)
os_ext(-Ext, -Stem, +File)
 os_ext(?Ext, +New, +File, -NewFile)
os_ext(?Ext, ?Stem, ?File, +Opts)
Switch the position of the first two arguments of file_name_extension/3. Ext is the file extension of File. Provides an arity 2 version of file_name_extension/3. This is appropriate for going through include/3 to filter files of a kind in a list of filenames. In os_ext/3, Ext is not added if it is already the extension to Stem (new in 0.2). In os_ext/4, if the last argument is ground, it is taken to be Opts.

New is a replacement of Ext in File that produces NewFile. Contrary to file_name_extension/3, os_ext/3 allows dots in Ext.

Currently Opts is only used to provide a trail of who has called this predicate for error reporting purposes:

 ?- os_ext( _, _, _, os_mill/4 ).
 ERROR: os:os_ext/4: Ground arguments expected in some of the positions: [[1,2],3], but found:[_172,_178,_184]
 ERROR: Trail: [os_mill/4]

 ?- os_ext( _, _, _, [os_mill/4,hrsv_fda/1] ).
 ERROR: os:os_ext/4: Ground arguments expected in some of the positions: [[1,2],3], but found:[_2126,_2132,_2138]
 ERROR: Trail: [os_mill/4,hrsv_fda/1]


  ?- file_name_extension( X, tar.gz, abc.tar.gz ).
  ?- os_ext( tar.gz, Stem, dir/abc.tar.gz ).
  Stem = dir/abc

  ?- include( os_ext(pl), ['what.pl',none], Pls ).
  Pls = ['what.pl'].

  ?- maplist( os_ext(xls,csv), [abc.xls,def.xls], New ).
  New = [abc.csv, def.csv].

  ?- os_ext( Ext, library(abc.txt) ).
  Ext = txt.

  ?- os_ext( csv, file.csv, File ).
  File = file.csv.

  ?- os_ext( csv, a.file.csv, File ).
 File = a.file.csv.

 ?- os_ext( X, S, a.file.csv ).
 X = csv,
 S = a.file.

 ?- os_ext( Old, new, afile.csv, Afile ).
 Old = csv,
 Afile = afile.new.

 ?- os_ext( Old, new, library(afile.csv), Afile ).
 Old = csv,
 Afile = library(afile.new).

 ?- os_ext( csv, abc/def, Os ).
 Os = abc/def.csv.
 ?- os_ext( csv, library(def), Os ).
 Os = library(def.csv).

 ?- os_ext( csv, /def, Os ).
 Os = /def.csv.
 ?- os_ext( csv, abc/def, Os ).
 Os = abc/def.csv.
 ?- os_ext( csv, "abc/def", Os ).
 Os = "abc/def.csv".
 ?- os_ext( csv, "abc/def", +Os ).
 Os = 'abc/def.csv'.
 ?- os_ext( Ext, Stem, "afile.csv" ).
 Ext = "csv",
 Stem = "afile".

 ?- os_ext( srt, 'a.file', 'a.file.srt' ).

 ?- os_ext( Old, srt, 'a.file', Afile ).
 Old = file,
 Afile = a.srt.
 ?- os_ext( txt, Csv, old.txt, New ).
 ERROR: os:os_ext/4: Ground argument expected at position: 2,  (found: _7644)

 ?- os_ext( txt, Csv, New ).
 ERROR: os:os_ext/3: Ground arguments expected in some of the positions: [[1,2],3], but found:[txt,_8390,_8396]
- nicos angelopoulos
- 0.2 2015/05/18 changed behaviour to not adding Ext if it is already in Stem.
- 0.3 2016/02/05 added some os typing, and ground Ext can be multi doted
- 0.4 2016/01/04 various teething problems after public release
- 1.0 2018/10/11 allows 4th arg to be options. Errors updates, and trails.
 os_remove(+File, +OptS)
 os_rm(+File, +OptS)
File will be deleted if it exists. Extends built-in delete_file/1.
Version 1.2 added Options. Opts could be a single option term, see options_append/4.
Version 1.3 provides better messaging via throw/2.


controls, messaging and execution if file does not exist (via throw/2). values: error, test, exists; or use options on_exit(E) message(M)
allows informational message printing for deleting and failed operation
?- os_remove( sk, true ).
Warning: os:os_rm/2: OS file: sk, does not exist

?- @touch(sk).

?- os_remove( sk, debug(true) ).
% Deleting existing file: sk

?- os_remove( sk, err(test) ).

?- os_remove( sk, err(error) ).
ERROR: os:os_rm/2: OS file: sk, does not exist

?- os_remove( sk, [on_exit(error),message(warning)] ).
Warning: os:os_rm/2: OS file: sk, does not exist

?- os_remove( sk, [on_exit(false),message(informational)] ).
% os:os_rm/2: OS file: sk, does not exist

?- os_remove( sk, [on_exit(fail),message(warning)] ).
Warning: os:os_rm/2: OS file: sk, does not exist
- Nicos Angelopoulos
- 0.1 2014/09/10
- 0.2 2018/10/1, use throw/2
 os_make_path(+Path, +Opts)
os_make_path(-Path, +Opts)
Mostly as make_directory_path/1, but with options. Also it can generate Path from its options.


when Afresh==true, predicate removes path before creating it afresh
The predicate only reports when directory did not exist by default. Turn this to true to print a debug message when the directory did exist and Dbg is true
as per options_append/4 when true it prints a debugging message. The predicate does not listen to debug(os_make_path), instead it uses options_debug/3.
operate on Dir if Path is a variable and Odir is not given
operate on Odir if Path is a variable (preceeds over Dir).
whether to move to newly created directory
do not go on with operation if MkPath is false
returns current directory (useful when Move == true)
?- os_make_path( '/tmp/what', debug(true)  ).
% Creating path: '/tmp/what'

?- os_make_path( '/tmp/what', debug(false)  ).

?- os_make_path( '/tmp/what', debug(true)  ).

?- os_make_path( '/tmp/what', debug_exists(true)  ).

?- os_make_path( '/tmp/what', [debug_exists(true),debug(true)]  ).
% Path existed: '/tmp/what'

?- os_make_path( '/tmp/what1', debug(false)  ).

?- os_make_path( '/tmp/what2' ).

?- os_make_path( "/tmp/what4" ).

?- os_make_path( /tmp/what5 ).

?- os_make_path( library(tmp) ).

?- os_make_path( Path, odir('/tmp/what3') ).
Path = '/tmp/what3'.

?- ls('/tmp/what3').

?- os_make_path( Path, true ).

ERROR: Domain error: `Path, first argument in os_path_make/2 ground, or options containing dir/1 or odir/2' expected, found `'Opts'=['$restore'(os_make_path,debug,false),true,debug(false),afresh(false),debug_exists(false)]'

?- os_make_path( '/tmp/new1', [make_path(false),debug(true)] ).
% Skipping creation of path: '/tmp/new1'
See also
- options_append/4 (pack(options))
- options_debug/3 (pack(options))
 os_mill(+FromOs, +Goal, ?Milled, +Opts)
Generate or recreate Milled from FromOs by calling Goal. If Milled is not given its name is generated by applying os_postfix/3 on FromOs with SepPsf as the postfix. Milled is usually a file, but it can also be a directory. By default, Goal is trusted to generate the correct format. If FromOs is of the form @(Callable), then call(Callable,Obj) is called to establish the location of OsObj which is used in place of FromOs.

As of 0.3, when Type=dir and OutsTo is a filename, Milled is created as to house the file. Goal, should be aware of this- as it might be attempting to create it too.

The Goal is called as call( Goal, RelFromOs, Milled, Co ).


the argument is passed as the last argument to Goal. (Set to false for no options argument on the call.)
at which module to call Goal
whether to print debugs (see options_append/4) (default does nothing)
directory for both Os and Milled (no default)
if given, change the extension in Milled (ignored for dirs), when Milled is ground the extension is added
use Goal's term name as the stem
returns the full milled OS name (file or directory)
check Os was created at exit; if not take action: true, error, fail, debug
directory for output (takes precendence over Dir)
callable that is qurried if Milled already exists, eg to load an R object from a saved file called as call(OnX,Milled), if OnX has a module, then it is used as is, else Mod is : prepended.
if a different filename is given, it is used for redirecting both user_output and user_error. When Type=dir OutsTo is assumed to be a file within (Milled, is created in this case).
only relevant when OutsTo is a filename. OutsTty=true makes the file a tty output (stream_property/2).
postfix for the new file name (see os_postfix/3)
whether to recreate OsEntry
separator for Psf
or dir for directory

The predicate uses os_dir_stem_ext/2 to construct OS, so its options can be used in addition to the above.

The default postfix (P) is taken to be the predicate name of Goal, minus a possible 'file_' prefix.

?- assert( (true(A,B,C) :- write(args(A,B,C)), nl) ).

?- os_mill( abc.txt, true, Outf, [] ).
ERROR: os:os_mill/4: OS milled: abc_true.txt  was not created (source was: abc.txt)

?- os_mill( abc.txt, true, Outf, not_created(fail) ).

?- use_module(library(debug)).

?- [user].
|: go_from_here_to_there( Here, There, HTopts ) :-
|:     debug( testo, 'Here: ~w', [Here] ),
|:     debug( testo, 'There: ~w', [There] ),
|:     debug( testo, 'TheOpts: ~w', [HTopts] ).
|: ^D% user://1 compiled 0.01 sec, 1 clauses

?- debug(testo).

?- Milts = [outputs_to('debug_outs.txt'),type(dir),debug(true)],
    os_mill( here, go_from_here_to_there, ex_os_milled, Milts ),
    write( milled(ex_os_milled) ), nl.
% Creating non-existing mill entity: ex_os_milled, from: here
% Calling os_mill: user:go_from_here_to_there/0
% Opened:'ex_os_milled/debug_outs.txt', at:<stream>(0x558dbb1d98c0)
% Changing channels to: io_streams(user_input,<stream>(0x558dbb1d98c0),<stream>(0x558dbb1d98c0))
% Closing: <stream>(0x558dbb1d98c0)
% Run output at: 'ex_os_milled/debug_outs.txt'
Milts = [outputs_to('debug_outs.txt'), type(dir), debug(true)].

?- shell( 'cat ex_os_milled/debug_outs.txt').
% Calling: call(user:go_from_here_to_there,here,ex_os_milled,[])
% Here: here
% There: ex_os_milled
% TheOpts: []
% Caught: exit
% Reverting streams to: io_streams(<stream>(0x7f4311820780),<stream>(0x7f4311820880),<stream>(0x7f4311820980))

- nicos angelopoulos
- 0.1 2014/10/15
- 0.2 2016/06/28
- 0.3 2020/09/16, added outputs_to() & outputs_as_tty(), with example
To be done
- double check non atomic File and Milled
 os_un_zip(+File, ?Stem, +Opts)
 os_un_zip(+File, ?Stem)
Does a simple and safe job of gunzipping files. If uncompressed file exists the predicate skips via default, but other behaviours can be defined via Opts.


see options_append/3
if true, pass Keep option to
skip: skips the un_zipping (with debug message), error: throws error quiet: skip with no debug message, redo: run the un_zip regardless
?- tell( ex.txt ), maplist( writeln, [1,2,3,4,5] ), told, shell( 'gzip ex.txt' ), ls.

?- os_un_zip( ex.txt.gz, Stem1, keep(true) ).
?- os_un_zip( ex.txt.gz, Stem2, on_exists(error) ).

Second time should be an error as the gunzipped file already exists.

?- tell( ex2.txt ), maplist( writeln, [1,2,3,4,5] ), told, shell( 'gzip ex2.txt' ), ls.
?- os_un_zip( ex2.txt.gz, Stem, [keep(true),debug(true)] ).
% Sending: gunzip(-k,-d,ex2.txt.gz)
Stem = ex2.txt.
% Stem: ex.txt exists, so skipping un_zipping of file: ex.txt.gz
Stem = ex.txt.

Second time the debug message is different.

- nicos angelopoulos
- 0.1 2014/7/2
- 0.2 2016/6/20, changed from gunzip/2 to os_un_zip/2, added options
To be done
- deal with .tgz files (do not need this yet)
- use different engines ? with auto-recognision from extensions ?
 os_parts(+Parts, -Stem)
os_parts(-Parts, +Stem)
 os_parts(?Parts, ?Stem, +Opts)
Construct and deconstruct stems of files from/to their constituents.


defaults to flag filename_separator by preference
shortened separator()
?- os_parts( Parts, abc_def ).
Parts = [abc, def].

?- os_parts( Parts, 'abc_def-xyz', sep(-) ).
Parts = [abc_def, xyz].
 os_path(+Dir, +File, -Path)
os_path(-Dir, -File, +Path)
 os_path(-Parts, +Path)
os_path(+Parts, -Path)
Mostly a polymorphic directory_file_path/3 nickname. Also '' is the default rather than '/' when dealing with absolute paths. The predicate understands all the input types known to os_name/2. Path takes its type from Dir and vice versa- when mode is reversed. If you require Path to be of specific type regardless of what Dir is decorate -Path as explained is os_name/2.
?- os_path( D, F,  '/abc/def/' ).
D = '/abc',
F = def.

?- os_path( D, F,  "/abc/def/" ).
D = "/abc",
F = "def".

?- os_path( D, F,  /abc/def/ ).
ERROR: Syntax error: Unbalanced operator
ERROR: os_path( D, F,  /abc/def/
ERROR: ** here **
ERROR:  ) .

?- os_path( D, F,  /abc/def ).
D = /abc,
F = def.

?- os_path( abc/def, ghi.txt, Path ).
Path = abc/def/ghi.txt.

?- os_path( /abc/def, ghi.txt, Path ).
Path = /abc/def/ghi.txt.

?- os_path( '', abc, Abc ).
Abc = abc.

?- directory_file_path( '', abc, Abc ).
   Abc = '/abc'.

?- os_path( Dir, Base, '/abc' ).
Dir = '',
Base = abc.

?- directory_file_path( Dir, Base, '/abc' ).
Dir =  (/),
Base = abc.

?- os_path( hmrn(library(what)), abc, Abc ).
ERROR: pack(os): Nested aliases are not supported yet.
Found: hmrn(library(what)) at position: 1 for predicate: os_path/3

?- file_search_path( hmrn, Hmrn ).
Hmrn = '/home/nicos/ac/14mg/cohorts/hmrn'.

?- os_path( hmrn(what), if, Abc ).
Abc = hmrn('what/if').

?- os_path( hmrn(what/if), not, Abc ).
Abc = hmrn(what/if/not).

?- os_path( hmrn(what/if), not, +Abc ).
Abc = '/home/nicos/ac/14mg/cohorts/hmrn/what/if/not'.

?- os_path( hmrn(what/if), not, \Abc ).
Abc = /home/nicos/ac/'14mg'/hmrn/what/if/not.

?- os_path( hmrn(what/if), not, @Abc ).
Abc = hmrn(what/if/not).

?- os_path( "what/if", foo.txt, Abc ).
Abc = "what/if/foo.txt".

?- os_path( Parts, 'a/b/c.txt' ), os_path( Parts, Rel ).
Parts = [a, b, c.txt],
Rel = 'a/b/c.txt'.

?- os_path( Parts, '/a/b/c.txt' ), os_path( Parts, Rel ).
Parts = ['', a, b, c.txt],
Rel = '/a/b/c.txt'.
- nicos angelopoulos
- 0.4 2020/9/14
 os_postfix(-Postfix, +Posted)
 os_postfix(+Postfix, +Fname, -Posted)
os_postfix(+Postfix, -Fname, +Posted)
 os_postfix(+Postfix, ?Fname, ?Posted, +Opts)
os_postfix(+Postfix, +Opts, ?Fname, ?Posted)
Append a Postfix atom (or list of postfix atoms) to a filename without touching its file type extension. Also works for removing a postfix. If Postfix is compound of arity/1 is taken to be an aliased path, in which case the innermost path is extended.

Second argument is allowed to be the options (recognised as such when input is a list) so that it can be used in meta-calls.


if Ext is ground is assumed to be the strippable part of Fname. When Ext is a variable the found extension is bound to it. This option is for testing and returning, see with_ext() for setting alternative extensions.
(separated) parts to ignore at end (see ex. below)
replace extension of Fname with WithExt, if ground (you can pick up the old one using an unbound varible in ext(Ext) above)
alternative way of defining Postfix, only used when Postfix is a variable
replace relevant part of filename instead of adding new postfix
expansion of sep(Sep) - canonical is sep(Sep)
shortened separator() separator for stem-file parts (see os_sep/2)
 ?- os_postfix( abc, library(x.txt), T ).
 T = library(x_abc.txt).
 ?- os_postfix( abc, library(x.txt), T, [separator(-)] ).
 T = library('x-abc.txt').
 ?- os_postfix( abc, x.txt, T, sep(.) ).
 T = x.abc.txt.
 ?- os_postfix( v1, graph_layout.csv, T, [ignore_post(layout)] ).
 T = graph_v1_layout.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, [ignore_post(layout)] ).
 T = graph_lay_out_v1.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, ignore_post([lay,out]) ).
 T = graph_v1_lay_out.csv.
 ?- os_postfix( v1, graph_lay_out.csv, T, [ignore_post([out]),replace(true)] ).
 T = graph_v1_out.csv
 ?- maplist( os_postfix(v1,[sep(-)]),[a.csv,b.csv], AB ).
 AB = ['a-v1.csv', 'b-v1.csv'].
 ?- os_postfix( _, library(x.txt), T, postfix(abc) ).
 T = library(x_abc.txt).
 ?- os_postfix( _, "x.txt", T, postfix(abc) ).
 T = "x_abc.txt".
 ?- os_postfix( Psf, abc_def.txt ).
 Psf = def.
 ?- os_postfix( bit, by.csv, ByBit, with_ext(txt) ).
 ByBit = by_bit.txt.
 ?- os_postfix( [by,bit], bit.csv, ByBit, with_ext(txt) ).
 ByBit = bit_by_bit.txt.
- nicos angelopoulos
- 0.2 2014/7/8 changed order of 1&2 make it more suitable to meta calls
- 0.3 2014/7/28 added aliased paths and example
- 0.4 2014/12/2 added options (separator/1,ignore_post/1,replace/1)
 os_repoint(+Link, +Target)
Repoint an existing Link to a new Target. Pred fails if Link exists but not a link. Pred is debug(os_repoint) aware.
?- debug( os_repoint ).
?- shell( 'touch atzoumbalos' ).
?- shell( 'ln -s atzoumbalos shortos' ).
?- shell( 'touch atzoukos ).
?- os_repoint( shortos, atzoukos ).
% Warning, repointing link did not exist. Creating: shortos
% Linked to: '/home/nicos/pl/packs/private/os/atzoukos'
?- os_repoint( shortolos, atzoumbalos ).
% Repointing existing link: shortolos
% Old target was: '/home/nicos/pl/packs/private/os/atzoukos'
% Linked to: '/home/nicos/pl/packs/private/os/atzoumbalos'
?- os_repoint( danglink, atzou ).
% Warning, repointing link did not exist. Creating: danglink
% Linked to: '/home/nicos/pl/packs/private/os/atzou'
?- exists_file( danglink ).
?- os_exists( danglink ).
?- os_exists( danglink, type(flink) ).
?- os_exists( danglink, type(link) ).
- nicos angelopoulos
- 0.1 2014/7/23
See also
- was repoint_link/2
To be done
- extend interface to control link_file/3 3rd argument?
 os_slashify(-Path, +Slashed)
os_slashify(+Path, -Slashed)
Convert between Slashify and ensure de-slashified versions of os entry names. The predicate does not check the arguments map to os entries it works purely on the atomic or string representation. Path may be represented by an os slash-term structure or alias compound (see os_term/2) but Slashed is always either atomic (in most cases) or string in the case where Path is string. You can force casting as per os_cast/2.
0.2@2011/10/28, '' now goes to '', not to '/'

   0.3@2013/10/06, now also goes -Path +Slashed

   0.4@2014/10/06, changed predicate name from slashify/2
?- os_slashify( a, A ).
A = 'a/'.

?- os_slashify( "a", A ).
A = "a/".

?- os_slashify( A, 'a/' ).
A = a.

?- os_slashify( A, 'a' ).
A = a.

?- os_slashify( library(csv), Sla ).
Sla = '/home/nicos/pl/lib/src/csv/'.

?- os_slashify( /tmp/abc, &(Sla) ).
Sla = "/tmp/abc/".

?- os_slashify( /tmp/abc, Atom ).
Atom = '/tmp/abc/'.

?- os_slashify( /tmp/abc, \(Term) ).
Term = /tmp/abc.
- nicos angelopoulos
- 0.5 2016/2/23
 os_term(+Atom, -Term)
os_term(-Atom, +Term)
Bi-directional convertion between atom and slash-term representations of Os entries. Can also be used to ensure the type.
 ?- os_term( Atom, './abc/edf.g' ).
 Atom = './abc/edf.g/'.
 ?- os_term( Atom, '.'/abc/edf.g ).
 Atom = './abc/edf.g/'.

 ?- os_term( 'abc/edf.g', Term ), Term = B / C.
 Term = abc/edf.g,
 B = abc,
 C = edf.g.
 ?- os_term( Abc, /abc/def.txt ).
 Abc = '/abc/def.txt'.
 ?- os_term( Abc, abc/def.txt ).
 Abc = 'abc/def.txt'.

 % Can be used to ensure a dir is in atom form:
 ?- os_term( Atom, 'abc/def' ).
 Atom = 'abc/def'.
 ?- os_term( '/abc/edf.g', Term ), Term = /A/B .
 ? Term = /abc/edf.g,
 A = abc,
 B = edf.g.

 ?- os_term( './abc/edf.g', Term ).
 Term = ('.')/abc/edf.g.

v0.2 added

- nicos angelopoulos
- 0.2 2014/9/16,
Creates a uniquely named directory.

Contrary to system tmp_file_stream/3 this predicate does not remove the directory at halt. The directory is placed in /tmp/ so it wouldn't survive a reboot.

- nicos angelopoulos
- 0.1 2014/4/2
 os_name(+Os, -NameType)
NameType is the type of name for Os. The possible types are atom, slash, string and alias. The alias type only succeeds if Os is a compound of arity one, and its functor matches a current known alias. When Os is a variable, Type is atom.

When Os matches +(_), \(_) , &(_) or @(_) then the corresponding type is atom, slash string and alias, respectively. In this case, then first argument is not inspected.


 ?- os_name(_,Type).
 Type = atom.
 ?- os_name(abc,Type).
 Type = atom.
 ?- os_name('abc/def',Type).
 Type = atom.
 ?- os_name("abc/def",Type).
 Type = string.
 ?- os_name(abc/def,Type).
 Type = slash.
 ?- os_name(+(_),Type).
 Type = atom.
 ?- os_name(\(_),Type).
 Type = slash.
 ?- os_name(&(_),Type).
 Type = string.
- nicos angelopoulos
- 0.1 2014/9/18, this used to return compound
- 0.2 2015/12/10, now we dissect compound to slash and alias. added errors.
 os_unique(+TokenS, -Os)
 os_unique(+TokenS, -Os, +Opts)
Create a unique file or directory named Os by using date or version elements and a Token.

TokenS can be an atomic Token, or a list of Tokens in which case the Separator option will also apply within the token parts.

The predicate does not only provide the name of Os, it also, by default, creates it.


by(By=date([ye, mo, da, [ho, mi], [se]]))
how to group: either by date with the default taking YeMoDa first, then adds HoMi and on the third attempt adds Seconds. For alterative ways to do dates give the respective date/3 term (date/0 is shorthand for default date pattern). For building unique via version give version(Pfx,Compon,Type,Whc) (version/0 is short for version(v,'',1,int,1)).
when false, do not make uniqueness check. A false value makes this predicate a misnomer, however is useful for geting the Os value in say results directory. See examples.
by default, Os is created
parent directory
extension to add to Os iff type is file
max length of date Id (ye,mo,da) if using By=date/n or integer if using By=version/n (Nth component), and an integer
min length of date Id (ye,mo,da) if using By=date/n or integer when By=verions/n (Nth component), and an integer
or after, where to place the token in relation to the date
how to conjoin Token and unique part (was token_sep)
inter date component separator (was date_sep)
separator to be used in bonding TokenS into Token
should the unique entry be a _dir_ectory or a file.

Id and DP can be a free variables in which case they match everything.

When using dates for By and call this twice within a single second there is all the chance it will fail.

?- os_unique( res, Dname ).
Dname = 'res-14.05.22'.

?- os_unique( res, Dname, [] ).
Dname = 'res-'.

?- os_unique( res, Dname, [] ).
Dname = 'res-'.

?- os_unique( res, Dname, [] ).
Dname = 'res-'.

?- os_unique( res, Dname, dir('/tmp') ).
Dname = 'res-21.02.15/'
% note: dir is created in /tmp

?- os_unique( res, Dname, [min_length(_,3)] ).
Dname = 'res-014.005.022'.

?- os_unique( res, Dname, [token_sep('+'),sep_sub(':'),place_token(after),type(file)] ).
Fname = '14:05:22+res.csv'.

?- os_unique( res, Dname, [token_sep('+'),sep_sub(':'),place_token(after),type(file)] ).
Dname = '14:05:22:11:03+res.csv'.

?- os_unique( tkn, &(Bname), [type(file),ext(tsv)] ).
Bname = "tkn-16.02.23.tsv".

?- os_unique( tkn, &(Bname), [type(file),ext(tsv)] ).
Bname = "tkn-".

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, [ext(png),by(version),create(false),type(file)] ).
Dname = 'res-v01.png'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v01'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v02'.

?- os_unique( res, Dname, by(version) ).
Dname = 'res-v03'.

?- os_unique( res, Dname, [by(version),create(false)] ).
Dname = 'res-v04'.

?- os_unique( res, Dname, [by(version),create(false)] ).
Dname = 'res-v04'.

To force a restriction of say uniqueness at the date level:

?- os_unique( res, Here, by(date([ye,mo,da])) ).
Here = 'res-23.06.01'.
?- os_unique( res, Here, by(date([ye,mo,da])) ).

As of version 0.6, you can instead do the above with an error

?- os_unique( res1, Here, check(false) ).
?- ls.
bigs.pl           data/             res1-23.06.01/
?- os_unique( res1, Here, check(false) ).
ERROR: directory `'res1-23.06.01'' does not exist (File exists)

Used to be unique_entry_by_date/n, then unique_by_date/n.

- nicos angelopoulos
- 0.3 2016/2/23
- 0.4 2016/9/2 changed name from os_unique_by_date, now it also does versioning
- 0.5 2021/2/15 added option dir()
- 0.6 2023/6/1 added option check()
 os_base(+Os, -Bname)
Like file_base_name/2 but also works for all formats accepted by os_lib
?- os_base( abc/foo.txt, Base ).
Base = foo.txt.

?- os_base( Var, Base ).
ERROR: pack(os): Ground argument expected at position: 1 for predicate: os_base/2, but, _G1156 was found

?- os_base( abc(foo.bar), Base ).
ERROR: pack(os): OS entity: abc(foo.bar), looks like aliased but alias does not exist.

?- os_base( library(foo.bar), Base ).
Base = foo.bar.

?- os_base( "abc/foo.txt", Base ).
Base = "foo.txt".

?- os_base( "abc/foo.txt", +Base ).
Base = foo.txt.
 os_cast(+Os, -Termplate)
 os_cast(+Def, +Os, -Termplate)
Cast Termplate's variable part to the Os entity as defined by Template's grounded part. If Template is a variable, Def is used as the type for casting Os onto the Template variable (via os_type_entity/3). In os_cast/2 version, Def defaults to atom.
?- os_cast( abc/edf.txt, +Var ).
Var = 'abc/edf.txt'.

?- os_cast( abc/edf.txt, \Var ).
Var = abc/edf.txt.

?- os_cast( abc/edf.txt, @Var ).
Var = abc/edf.txt.

?- os_cast( abc/edf.txt, &(Var) ).
Var = "abc/edf.txt".

?- os_cast( abc/edf.txt, Var ).
Var = 'abc/edf.txt'.

?- os_cast( atom, abc/edf.txt, Var ).
Var = 'abc/edf.txt'.


+(V) converts to atom (atom)

\(V) converts to /-term (slash)

@(V) leave Os as is, assumes it is an alias-term
it is harder to convert arbitrary terms to aliased ones, (alias)

&(V) converts to string (string)

See also
- os_type_entity/3 for how the types are converted
To be done
- /(V) was -(V)
- 0.2 2018/10/1 swapped 1 & 2 args in /3 version
 os_abs(+Os, -Abs)
 os_abs(+Os, -Abs, +Opts)
Short for absolute_file_name/2 but also when Os is '' it is not interpreted as '.'. Os can a / starting slash Os term (os_name/2). Note that absolute_file_name/2 deals correctly with all other os_name/2 types.
 os_file(?File, +Opts)
True iff File is a file or a link to an existing file, in the current directory.
Can be used to enumerate all files. The order is via sort/2.


Directory in which to find File.
Set to true if dot starting files are required.
Note that '.' and '..' are never returned.
If true, return the target of links rather than the links (via read_link/3).
This makes most sense when Stem==abs.
Alternatively set to findall for returning a list of all solutions.
What stem to add to returned files,
rel: relative, abs: absolute, false: no stem
Find files within sub directories when set to true.
 ?- cd(pack('os_lib/examples/testo')).

 ?- os_file(File).
 File = file1 ;

 ?- os_file( & File ).
 File = "file1".
 ?- os_file(File, sub(true)).
 File = 'dir1/file2' ;
 File = 'dir1/link2' ;
 File = file1.
 ?- os_file(File, dots(true)).
 File = '.dotty1' ;
 File = file1.
:- absolute_file_name( pack(os_lib), OsDir ), working_directory( Old, OsDir ).
 OsDir = '/usr/local/users/nicos/local/git/lib/swipl-7.7.19/pack/os_lib',
 Old = '/home/nicos/.unison/canonical/sware/nicos/git/github/stoics.infra/'.
 File = pack.pl ;
 ?- os_file( File, solutions(findall) ).
 File = [pack.pl].
 ?- os_file( File, [solutions(findall),sub(true)] ).
 File = ['doc/Releases.txt', 'doc/html/h1-bg.png', 'doc/html/h2-bg.png', 'doc/html/multi-bg.png', 'doc/html/os.html', 'doc/html/pldoc.css', 'doc/html/priv-bg.png', 'doc/html/pub-bg.png', 'examples/testo/dir1/file2'|...].
 ?- os_file( File, [solutions(single),sub(true)] ).
 File = 'doc/Releases.txt' ;
 File = 'doc/html/h1-bg.png' ;
 File = 'doc/html/h2-bg.png' ;
 File = 'doc/html/multi-bg.png'...
- nicos angelopoulos
- 0.1 2016/01/31, this version without ref to lib(os_sub)
- 0.2 2018/07/23, added options, dir(Dir) and sub(true)
- 0.3 2018/10/01, added option dots(Dots)
- 0.4 2018/11/04, added option solutions(Sol)
- 0.5 2022/02/05, pass Sol through known/1
- 0.6 2023/01/02, reverted passing solutions through known. solutions has types, which catches unknown values.
 os_files(-Files, +Opts)
Collects all files for which os_file(File) or os_file(File,Opts) succeed.
Opts are passed to os_file/2.
 ?- absolute_file_name( pack(os_lib/src), Abs ), os_files( Files, dir(Abs) ).
 Abs = '/usr/local/users/nicos/local/git/lib/swipl-7.7.18/pack/os_lib/src',
 Files = [os_abs.pl, os_base.pl, os_cast.pl, os_cp.pl, os_dir.pl, os_dir_stem_ext.pl, os_errors.pl, os_exists.pl, os_ext.pl|...].
- nicos angelopoulos
- 0.1 2016/1/31, this version without ref to lib(os_sub)
- 0.2 2018/8/05, added options, dir(Dir) and sub(true), removed os_dir_files/2
See also
- os_file/2
 os_dir(?OsDir, +Opts)
True iff OsDir is a directory or a link to an existing directory, in the current directory.
Directories '.' and '..' are not returned. Can be used to enumerate all directories.


directory in which to find OsDir.
set to true if dot starting dirs are required
note that '.' and '..' are never returned
or findall for returning a list for solutions
what stem to add to returned files, rel: relative (default), abs: absolute, false: none
find OsDir within sub directories when true
 ?-  cd( pack(os_lib) ).
 ?- ls.
 % doc/      pack.pl   prolog/   src/
 ?- os_dir(Dir), write( Dir ), nl, fail.
 ?- os_dir(& Dir).
 Dir = "doc" ;
 Dir = "prolog" ;
 Dir = "src" ;
 ?- os_dir(Os,sub(true)), write(Os), nl, fail.
 ?- os_dir(Os,[stem(abs),sub(true)]), write(Os), nl, fail.

 ?- cd(pack('os_lib/examples/testo')).
 ?- os_dir( Dir ).
 Dir = dir1 ;
 ?- os_dir( Dir, dots(true) ).
 Dir = '.dodi1'
 Unknown action: ' (h for help)
 Action? ;
 Dir = dir1 ;

 ?- absolute_file_name( pack(os_lib), OsDir ), working_directory( _, OsDir ).
 OsDir = '.../lib/swipl-7.7.19/pack/os_lib',
 ?- ls.
 % doc/        examples/   pack.pl     prolog/     src/
 ?- os_dir( Dir ).
 Dir = doc ;
 Dir = examples ;
 Dir = prolog ;
 Dir = src ;
 ?- os_dir( Dir, solutions(findall) ).
 Dir = [doc, examples, prolog, src].
- nicos angelopoulos
- 0.2 2016/1/31, this version without ref to lib(os_sub)
- 0.3 2018/8/05, added options and harmonized with os_file/2
- 0.3 2018/10/1, added option dots(D)
- 0.4 2018/11/4, added option solutions(D)
 os_dirs(-Dirs, +Opts)
Find all directories for which os_dir(Dir) succeeds.
Opts are passed to os_dir/2.
 ?- cd( pack(os_lib) ).
 ?- os_dirs( Dirs ).
    Dirs = [doc,prolog, src, doc].
- nicos angelopoulos
- 0.2 2016/1/31, this version without ref to lib(os_sub)
- 0.3 2018/8/05, removed os_dir_dirs/2, now these are simply a findall on os_dir/1,2.
See also
- os_dir/2
 os_exists(+Os, +Opts)
True if Os exists as an object in the filestore. When mode is requested, the predicate goes to the source and tries to effect operations of the appropriate mode to establish permissions. The only deviation is permission execute on files. By default the predicate uses access_file(Os,execute). See option WinsFileExec.

The predicate tries to stay compatible with system predicates, but it does introduces two new file types: flink and dlink, for file point link or file, and directory pointing link or directory.


parent directory
test for report and fail, fail for failing, error for throwing, true for success
(see options: err(E), on_exit(O) and message(M) in throw/2)
reverse polarity, if true require Os not to exist
in addition to Os existing, require file type-ness (dir,link,file,flink,dlink,any).
Can be used to return the type, when input is a variable.
Type = base(BaseType) streamline type to either file or dir (see os_type_base/2).
one of exist, read, write and append
alternatively, use fail for failure and error for error
?- os_exists( pack(os_lib/src) ).

?- os_exists( pack(os_lib/src), type(link) ).

?- set_prolog_flag( allow_dot_in_atom, true ).
?- os_exists( pack(os_lib/prolog/os.pl), type(file) ).

?- cd( pack('os_lib/examples/testo') ).

?- os_exists(file1).

?- os_exists( "file1" ).

?- os_exists( file2 ).

?- os_exists( file2, err(error) ).
ERROR: os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, err(exists) ).
Warning: os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, [on_exit(fail),message(warning)] ).
Warning: os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, [on_exit(error),message(informational)] ), writeln(later).
% os:os_exists/2: OS entity: file2, does not exist

?- os_exists( file2, not(true) ).

?- os_exists( file1, [not(true),err(error)] ).
ERROR: os:os_exists/2: OS entity: file1, already exists

?- os_exists( file1, type(dir) ).

?- os_exists( file1, [type(dir),err(error)] ).
ERROR: os:os_exists/2: OS entity: file1, not of requested type: dir, but has type: file

?- os_exists( file1, type(flink) ).

?- os_exists( file1, type(link) ).

?- os_exists( dir1/link2, type(link) ).

?- os_exists( dir1/link2, type(base(Base)) ).
Base = file.
 os_sep(-Sep, Opts)
Read the default or Opts provided separator.


expansion of sep(Sep) - canonical is sep(Sep)
shortened separator(). Sep is the separator for stem-file parts
?- os_sep( Sep ).
Sep = '_'.

?- os_sep( Sep, true ).
Sep = '_'.

?- os_sep( Sep, separator(x) ).
Sep = x.

?- os_sep( Sep, [separator(x),sep(y)] ).
Sep = y.
 os_sel(+Oses, +PatternS, -Sel)
 os_sel(+Oses, +PatternS, -Sel, +Opts)
Select a number of entries from Oses according to PatternS. Oses can be one of the following tokens: os_files, os_dirs or os_all or a list of Os objects. Tokens are expanded to the respective Os entries within the current directory. PatternS can be a list of

PatternS, a list of, or one of:

Oses with extension Ext
is a postfix of the stem of Os
is a prefix of the stem of Os
sub_atom/5 succeeds on the stem of the Os


directory at which the Oses will be sought
whether to return relative or absolute location
should sub dirs be recursed (passed to os_files/2 and os_dirs/2)
% mkdir /tmp/os_sel; cd /tmp/os_sel; touch a.pl b.txt c.pl abc.txt; mkdir abc_sub
?- os_sel( os_files, ext(pl), Sel, true ).
Sel = [a.pl, c.pl].

?- os_sel( os_files, ext(txt), Sel, true ).
Sel = [b.txt, abc.txt].

?- os_sel( os_all, sub(abc), Sel, true ).
Sel = [abc.txt, abc_sub].

?- working_directory( Old, '..' ), os_sel( os_dirs, os_, Sel, true ), working_directory( _, Old ).
Old = '/tmp/os_sel/',
Sel = [os_sel].

?- os_sel( os_files, ext(txt), Files, stem(abs) ).
Files = ['/homes/nicos/email.txt'].
- nicos angelopoulos
- 0.1 2016/ 8/24
- 0.2 2016/10/18 changed naked atoms to sub(Sub), added prefix and postfix
- 0.3 2017/2/8 allow list of patterns
- 0.4 2019/3/26 option sub()
- 0.5 2020/9/14 option stem()
 os_mv(+From, +To)
Move file from From to To. Behaves as unix mv, If To is a directory, From is moved into it (keeping From's basename).
% mkdir testo_D; touch testo_D/testo1; touch at_root
?- os_mv( testo_D/testo1, testo_D/example1 ).
?- os_mv( at_root, testo_D ).

?- ls( testo_D ).
% at_root    example1

% halt

άμπελος;src/os% rm testo_D/example1; rm testo_D/at_root
άμπελος;src/os% rmdir testo_D/;
- nicos angelopoulos
- 0.1 2016/7/
To be done
- debugging
 os_cp(+From, +To)
Copy file from From to To. Behaves as unix cp, If To is a directory, From is moved into it (keeping From's basename).
% mkdir testo_D; touch testo_D/testo1; touch at_root
?- os_cp( testo_D/testo1, testo_D/example1 ).
?- os_cp( at_root, testo_D ).

?- ls( testo_D ).
% at_root    example1   testo1

% halt

άμπελος;src/os% rm testo_D/testo1; rm testo_D/example1; rm testo_D/at_root; rm at_root
άμπελος;src/os% rmdir testo_D
- nicos angelopoulos
- 0.1 2016/8/25
To be done
- debugging
 os_ln_s(From, To)
Symbolic link file From to location To.
 os_type_base(?Type, ?Base)
Map detailed Type to Base type.

file,flink,link,any map to file and dir,dlink,link,any to dir. Predicate is deterministic and in the -,- mode it generates file for both any,link.

?- os_type_base( flink, Base ).
Base = file.

?- os_type_base( file, Base ).
Base = file.

?- cd( pack('os_lib/examples/testo') ).
?- os_exists( dir1/link2, type(base(Base)) ).
Base = file.
- nicos angelopoulos
- 0:1 2020/09/17
See also
- os_exists/2
 os_version(-Version, -Date)
Current version and release date for the pack.
?- os_version( V, D ).
V = 1:7:0,
D = date(2024,1,7)