View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           https://www.swi-prolog.org
    6    Copyright (c)  1985-2026, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(shell,
   39          [ shell/0,
   40            ls/0,
   41            ls/1,                               % +Pattern
   42            cd/0,
   43            cd/1,                               % +Dir
   44            pushd/0,
   45            pushd/1,                            % +Dir
   46            dirs/0,
   47            pwd/0,
   48            popd/0,
   49            mv/2,                               % +File1, +File2
   50            rm/1,                               % +File1
   51            cls/0,
   52
   53            shell_command/1                     % -Shell
   54          ]).   55:- autoload(library(apply),[maplist/3,maplist/2]).   56:- autoload(library(error),
   57	    [existence_error/2,instantiation_error/1,must_be/2]).   58:- autoload(library(lists),[nth1/3]).   59
   60:- multifile
   61    file_style/2.                               % ++FileName, =Style
   62
   63
   64:- set_prolog_flag(generate_debug_info, false).

Elementary shell commands

This library provides some basic (POSIX) shell commands defined in Prolog, such as pwd and ls for situations where there is no shell available or the shell output cannot be captured. Note that in recent versions (since 10.1.13) the GUI frontend swipl-win can capture process output on all supported platforms.

 shell
Execute an interactive shell. Uses shell_command/1 to find a suitable shell.

The shell's exit status is not our business, so shell/0 succeeds whatever it is. Note that if the shell cannot be executed shell/2 raises an exception.

   84shell :-
   85    shell_command(Shell),
   86    shell(Shell, _).
 shell_command(-Shell:atom)
True when Shell is the preferred executable for running an interactive shell. Used by shell/0. The following options are tried to find a suitable shell command:
  1. The Prolog flag shell
  2. The environment variable %comspec% (Windows only)
  3. The environment variable $SHELL
  4. The Prolog flag posix_shell
Errors
- existence_error(config, shell) if no suitable shell can be found.
  102shell_command(Shell) :-
  103    shell_command_(Shell),
  104    access_file(Shell, execute),
  105    !.
  106shell_command(_) :-
  107    existence_error(config, shell).
  108
  109shell_command_(Shell) :-
  110    current_prolog_flag(shell, Shell).
  111:- if(current_prolog_flag(windows, true)).  112shell_command_(Shell) :-
  113    getenv(comspec, WinPath),
  114    prolog_to_os_filename(Shell, WinPath).
  115:- endif.  116shell_command_(Shell) :-
  117    getenv('SHELL', Shell).
  118shell_command_(Shell) :-
  119    current_prolog_flag(posix_shell, Shell).
 cd
 cd(Dir)
Change working directory
  127cd :-
  128    cd(~).
  129
  130cd(Dir) :-
  131    name_to_file(Dir, Name),
  132    working_directory(_, Name).
 pushd
 pushd(+Dir)
 popd
 dirs
Manage the directory stack:
  147:- dynamic
  148    stack/1.  149
  150pushd :-
  151    pushd(+1).
  152
  153pushd(N) :-
  154    integer(N),
  155    !,
  156    findall(D, stack(D), Ds),
  157    (   nth1(N, Ds, Go),
  158        retract(stack(Go))
  159    ->  pushd(Go),
  160        print_message(information, shell(directory(Go)))
  161    ;   warning('Directory stack not that deep', []),
  162        fail
  163    ).
  164pushd(Dir) :-
  165    name_to_file(Dir, Name),
  166    working_directory(Old, Name),
  167    asserta(stack(Old)).
  168
  169popd :-
  170    retract(stack(Dir)),
  171    !,
  172    working_directory(_, Dir),
  173    print_message(information, shell(directory(Dir))).
  174popd :-
  175    warning('Directory stack empty', []),
  176    fail.
  177
  178dirs :-
  179    working_directory(WD, WD),
  180    findall(D, stack(D), Dirs),
  181    maplist(dir_name, [WD|Dirs], Results),
  182    print_message(information, shell(file_set(Results))).
 pwd
Print current working directory
  188pwd :-
  189    working_directory(WD, WD),
  190    print_message(information, format('~w', [WD])).
  191
  192dir_name('/', '/') :- !.
  193dir_name(Path, Name) :-
  194    atom_concat(P, /, Path),
  195    !,
  196    dir_name(P, Name).
  197dir_name(Path, Name) :-
  198    current_prolog_flag(unix, true),
  199    expand_file_name('~', [Home0]),
  200    (   atom_concat(Home, /, Home0)
  201    ->  true
  202    ;   Home = Home0
  203    ),
  204    atom_concat(Home, FromHome, Path),
  205    !,
  206    atom_concat('~', FromHome, Name).
  207dir_name(Path, Path).
 ls
 ls(+Pattern)
Listing similar to Unix ls -F, flagging directories with /. If the terminal supports it, Prolog source files are rendered as links using the OSC 8 escape sequence. When using Epilog, clicking a Prolog file opens it in the editor.
  217ls :-
  218    ls('.').
  219
  220ls(Spec) :-
  221    name_to_files(Spec, Matches),
  222    ls_(Matches).
  223
  224ls_([]) :-
  225    !,
  226    warning('No Match', []).
  227ls_([Dir]) :-
  228    exists_directory(Dir),
  229    !,
  230    atom_concat(Dir, '/*', Pattern),
  231    expand_file_name(Pattern, Files),
  232    maplist(tagged_file_in_dir, Files, Results),
  233    print_message(information, shell(file_set(Results))).
  234ls_(Files) :-
  235    maplist(tag_file, Files, Results),
  236    print_message(information, shell(file_set(Results))).
  237
  238tagged_file_in_dir(File, Result) :-
  239    file_base_name(File, Base),
  240    (   exists_directory(File)
  241    ->  atom_concat(Base, /, Label),
  242        Result = dir(File, Label)
  243    ;   read_link(File, _, _)
  244    ->  atom_concat(Base, '@', Label),
  245        Result = file(File, Label)
  246    ;   Result = file(File, Base)
  247    ).
  248
  249tag_file(File, dir(File, Label)) :-
  250    exists_directory(File),
  251    !,
  252    atom_concat(File, /, Label).
  253tag_file(File, file(File,File)).
 mv(+From, +To) is det
Move (Rename) a file. If To is a directory, From is moved into the directory. Uses expand_file_name/2 on the From argument.
  260mv(From, To) :-
  261    name_to_files(From, Src),
  262    name_to_new_file(To, Dest),
  263    mv_(Src, Dest).
  264
  265mv_([One], Dest) :-
  266    \+ exists_directory(Dest),
  267    !,
  268    rename_file(One, Dest).
  269mv_(Multi, Dest) :-
  270    (   exists_directory(Dest)
  271    ->  maplist(mv_to_dir(Dest), Multi)
  272    ;   print_message(warning, format('Not a directory: ~w', [Dest])),
  273        fail
  274    ).
  275
  276mv_to_dir(Dest, Src) :-
  277    file_base_name(Src, Name),
  278    atomic_list_concat([Dest, Name], /, Target),
  279    rename_file(Src, Target).
 rm(+File) is det
Remove (unlink) a file
  285rm(File) :-
  286    name_to_file(File, A),
  287    delete_file(A).
 name_to_file(+Name, -File)
Convert Name into a single file.
  294name_to_file(Spec, File) :-
  295    name_to_files(Spec, Files),
  296    (   Files = [File]
  297    ->  true
  298    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  299        fail
  300    ).
  301
  302name_to_new_file(Spec, File) :-
  303    name_to_files(Spec, Files, false),
  304    (   Files = [File]
  305    ->  true
  306    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  307        fail
  308    ).
  309
  310name_to_files(Spec, Files) :-
  311    name_to_files(Spec, Files, true).
  312name_to_files(Spec, Files, Exists) :-
  313    name_to_files_(Spec, Files, Exists),
  314    (   Files == []
  315    ->  print_message(warning, format('No match: ~w', [Spec])),
  316        fail
  317    ;   true
  318    ).
  319
  320name_to_files_(Spec, Files, _) :-
  321    compound(Spec),
  322    compound_name_arity(Spec, _Alias, 1),
  323    !,
  324    findall(File,
  325            (   absolute_file_name(Spec, File,
  326                                   [ access(exist),
  327                                     file_type(directory),
  328                                     file_errors(fail),
  329                                     solutions(all)
  330                                   ])
  331            ;   absolute_file_name(Spec, File,
  332                                   [ access(exist),
  333                                     file_errors(fail),
  334                                     solutions(all)
  335                                   ])
  336            ),
  337            Files).
  338name_to_files_(Spec, Files, Exists) :-
  339    file_name_to_atom(Spec, S1),
  340    expand_file_name(S1, Files0),
  341    (   Exists == true,
  342        Files0 == [S1],
  343        \+ access_file(S1, exist)
  344    ->  warning('"~w" does not exist', [S1]),
  345        fail
  346    ;   Files = Files0
  347    ).
  348
  349file_name_to_atom(Spec, File) :-
  350    atomic(Spec),
  351    !,
  352    atom_string(File, Spec).
  353file_name_to_atom(Spec, File) :-
  354    phrase(segments(Spec), L),
  355    atomic_list_concat(L, /, File).
  356
  357segments(Var) -->
  358    { var(Var),
  359      !,
  360      instantiation_error(Var)
  361    }.
  362segments(A/B) -->
  363    !,
  364    segments(A),
  365    segments(B).
  366segments(A) -->
  367    { must_be(atomic, A) },
  368    [ A ].
 warning(+Fmt, +Args:list) is det
  372warning(Fmt, Args) :-
  373    print_message(warning, format(Fmt, Args)).
  374
  375:- multifile prolog:message//1.  376
  377prolog:message(shell(file_set(Files))) -->
  378    { catch(tty_size(_, Width), _, Width = 80)
  379    },
  380    table(Files, Width).
  381prolog:message(shell(directory(Path))) -->
  382    { dir_name(Path, Name) },
  383    [ '~w'-[Name] ].
 table(+List, +Width)//
Produce a tabular layout to list all elements of List on lines with a maximum width of Width. Elements are placed as ls does:
1  4  7
2  5  8
3  6
  396table(List, Width) -->
  397    { table_layout(List, Width, Layout),
  398      compound_name_arguments(Array, a, List)
  399    },
  400    table(0, Array, Layout).
  401
  402table(I, Array, Layout) -->
  403    { Cols = Layout.cols,
  404      Index is I // Cols + (I mod Cols) * Layout.rows + 1,
  405      (   (I+1) mod Cols =:= 0
  406      ->  NL = true
  407      ;   NL = false
  408      )
  409    },
  410    (   { arg(Index, Array, Item) }
  411    ->  table_cell(Item, Layout.col_width, NL)
  412    ;   []
  413    ),
  414    (   { I2 is I+1,
  415          I2 < Cols*Layout.rows
  416        }
  417    ->  (   { NL == true }
  418        ->  [ nl ]
  419        ;   []
  420        ),
  421        table(I2, Array, Layout)
  422    ;   []
  423    ).
  424
  425table_cell(Item, ColWidth, false) -->
  426    { label_length(Item, Len),
  427      Spaces is ColWidth - Len
  428    },
  429    table_cell_value(Item),
  430    [ '~|~t~*+'-[Spaces] ].
  431table_cell(Item, _ColWidth, true) -->
  432    table_cell_value(Item).
  433
  434table_cell_value(dir(_, Label)) ==>
  435    [ '~w'-[Label] ].
  436table_cell_value(file(File, Label)) ==>
  437    (   { file_style(File, Style) }
  438    ->  (   { Style == url }
  439        ->  [ url(File,Label) ]
  440        ;   [ ansi(Style, '~w', [Label]) ]
  441        )
  442    ;   [ '~w'-[Label] ]
  443    ).
 file_style(++File, =Style) is det
True when File should be listed as a terminal hyperlink. The default only links Prolog source files.
Arguments:
Style- is either url to make a hyperlink or a valid style argument for ansi_format/3.
  453file_style(File, url) :-
  454    file_name_extension(_, Ext, File),
  455    link_file_extension(Ext),
  456    !.
  457
  458link_file_extension(Ext) :-
  459    user:prolog_file_type(Ext,source).
 table_layout(+Items, +PageWidth, -Layout:dict) is det
Compute the number of columns, rows and the column width to create a tabular layout for Items.
  466table_layout(Atoms, Width, _{cols:Cols, rows:Rows, col_width:ColWidth}) :-
  467    length(Atoms, L),
  468    longest(Atoms, Longest),
  469    Cols is max(1, Width // (Longest + 3)),
  470    Rows is integer(L / Cols + 0.49999),    % should be ceil/1
  471    ColWidth is Width // Cols.
  472
  473longest(List, Longest) :-
  474    longest(List, 0, Longest).
  475
  476longest([], M, M) :- !.
  477longest([H|T], Sofar, M) :-
  478    label_length(H, L),
  479    L >= Sofar,
  480    !,
  481    longest(T, L, M).
  482longest([_|T], S, M) :-
  483    longest(T, S, M).
  484
  485label_length(dir(_, Label), Len) =>
  486    atom_length(Label, Len).
  487label_length(file(_, Label), Len) =>
  488    atom_length(Label, Len).
 cls
CLear Screen. Emits ANSI control characters to clear the terminal.
  494cls :-
  495    format(user_error, '\e[3J\e[H\e[2J', []),
  496    format(user_error, '\e[3J\r', [])