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:- autoload(library(apply),[maplist/3,maplist/2]).   54:- autoload(library(error),
   55	    [existence_error/2,instantiation_error/1,must_be/2]).   56:- autoload(library(lists),[nth1/3]).   57
   58:- multifile
   59    file_style/2.                               % ++FileName, =Style
   60
   61
   62:- 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.

 shell
Execute an interactive shell. 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

The shell's exit status is not our business, so shell/0 succeeds whatever it is.

Errors
- existence_error(config, shell) if no suitable shell can be found.
   87shell :-
   88    interective_shell(Shell),
   89    access_file(Shell, execute),
   90    !,
   91    shell(Shell, _).
   92shell :-
   93    existence_error(config, shell).
   94
   95interective_shell(Shell) :-
   96    current_prolog_flag(shell, Shell).
   97interective_shell(Shell) :-
   98    current_prolog_flag(windows, true),
   99    getenv(comspec, Shell).             % first: $SHELL and posix_shell
  100                                        % name POSIX paths that a Windows
  101                                        % box may well resolve without
  102                                        % being able to execute them.
  103                                        % Set the `shell` flag to pick
  104                                        % another one.
  105interective_shell(Shell) :-
  106    getenv('SHELL', Shell).
  107interective_shell(Shell) :-
  108    current_prolog_flag(posix_shell, Shell).
 cd
 cd(Dir)
Change working directory
  116cd :-
  117    cd(~).
  118
  119cd(Dir) :-
  120    name_to_file(Dir, Name),
  121    working_directory(_, Name).
 pushd
 pushd(+Dir)
 popd
 dirs
Manage the directory stack:
  136:- dynamic
  137    stack/1.  138
  139pushd :-
  140    pushd(+1).
  141
  142pushd(N) :-
  143    integer(N),
  144    !,
  145    findall(D, stack(D), Ds),
  146    (   nth1(N, Ds, Go),
  147        retract(stack(Go))
  148    ->  pushd(Go),
  149        print_message(information, shell(directory(Go)))
  150    ;   warning('Directory stack not that deep', []),
  151        fail
  152    ).
  153pushd(Dir) :-
  154    name_to_file(Dir, Name),
  155    working_directory(Old, Name),
  156    asserta(stack(Old)).
  157
  158popd :-
  159    retract(stack(Dir)),
  160    !,
  161    working_directory(_, Dir),
  162    print_message(information, shell(directory(Dir))).
  163popd :-
  164    warning('Directory stack empty', []),
  165    fail.
  166
  167dirs :-
  168    working_directory(WD, WD),
  169    findall(D, stack(D), Dirs),
  170    maplist(dir_name, [WD|Dirs], Results),
  171    print_message(information, shell(file_set(Results))).
 pwd
Print current working directory
  177pwd :-
  178    working_directory(WD, WD),
  179    print_message(information, format('~w', [WD])).
  180
  181dir_name('/', '/') :- !.
  182dir_name(Path, Name) :-
  183    atom_concat(P, /, Path),
  184    !,
  185    dir_name(P, Name).
  186dir_name(Path, Name) :-
  187    current_prolog_flag(unix, true),
  188    expand_file_name('~', [Home0]),
  189    (   atom_concat(Home, /, Home0)
  190    ->  true
  191    ;   Home = Home0
  192    ),
  193    atom_concat(Home, FromHome, Path),
  194    !,
  195    atom_concat('~', FromHome, Name).
  196dir_name(Path, Path).
 ls
 ls(+Pattern)
Listing similar to Unix =ls -F=, flagging directories with =/=.
  203ls :-
  204    ls('.').
  205
  206ls(Spec) :-
  207    name_to_files(Spec, Matches),
  208    ls_(Matches).
  209
  210ls_([]) :-
  211    !,
  212    warning('No Match', []).
  213ls_([Dir]) :-
  214    exists_directory(Dir),
  215    !,
  216    atom_concat(Dir, '/*', Pattern),
  217    expand_file_name(Pattern, Files),
  218    maplist(tagged_file_in_dir, Files, Results),
  219    print_message(information, shell(file_set(Results))).
  220ls_(Files) :-
  221    maplist(tag_file, Files, Results),
  222    print_message(information, shell(file_set(Results))).
  223
  224tagged_file_in_dir(File, Result) :-
  225    file_base_name(File, Base),
  226    (   exists_directory(File)
  227    ->  atom_concat(Base, /, Label),
  228        Result = dir(File, Label)
  229    ;   Result = file(File, Base)
  230    ).
  231
  232tag_file(File, dir(File, Label)) :-
  233    exists_directory(File),
  234    !,
  235    atom_concat(File, /, Label).
  236tag_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.
  243mv(From, To) :-
  244    name_to_files(From, Src),
  245    name_to_new_file(To, Dest),
  246    mv_(Src, Dest).
  247
  248mv_([One], Dest) :-
  249    \+ exists_directory(Dest),
  250    !,
  251    rename_file(One, Dest).
  252mv_(Multi, Dest) :-
  253    (   exists_directory(Dest)
  254    ->  maplist(mv_to_dir(Dest), Multi)
  255    ;   print_message(warning, format('Not a directory: ~w', [Dest])),
  256        fail
  257    ).
  258
  259mv_to_dir(Dest, Src) :-
  260    file_base_name(Src, Name),
  261    atomic_list_concat([Dest, Name], /, Target),
  262    rename_file(Src, Target).
 rm(+File) is det
Remove (unlink) a file
  268rm(File) :-
  269    name_to_file(File, A),
  270    delete_file(A).
 name_to_file(+Name, -File)
Convert Name into a single file.
  277name_to_file(Spec, File) :-
  278    name_to_files(Spec, Files),
  279    (   Files = [File]
  280    ->  true
  281    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  282        fail
  283    ).
  284
  285name_to_new_file(Spec, File) :-
  286    name_to_files(Spec, Files, false),
  287    (   Files = [File]
  288    ->  true
  289    ;   print_message(warning, format('Ambiguous: ~w', [Spec])),
  290        fail
  291    ).
  292
  293name_to_files(Spec, Files) :-
  294    name_to_files(Spec, Files, true).
  295name_to_files(Spec, Files, Exists) :-
  296    name_to_files_(Spec, Files, Exists),
  297    (   Files == []
  298    ->  print_message(warning, format('No match: ~w', [Spec])),
  299        fail
  300    ;   true
  301    ).
  302
  303name_to_files_(Spec, Files, _) :-
  304    compound(Spec),
  305    compound_name_arity(Spec, _Alias, 1),
  306    !,
  307    findall(File,
  308            (   absolute_file_name(Spec, File,
  309                                   [ access(exist),
  310                                     file_type(directory),
  311                                     file_errors(fail),
  312                                     solutions(all)
  313                                   ])
  314            ;   absolute_file_name(Spec, File,
  315                                   [ access(exist),
  316                                     file_errors(fail),
  317                                     solutions(all)
  318                                   ])
  319            ),
  320            Files).
  321name_to_files_(Spec, Files, Exists) :-
  322    file_name_to_atom(Spec, S1),
  323    expand_file_name(S1, Files0),
  324    (   Exists == true,
  325        Files0 == [S1],
  326        \+ access_file(S1, exist)
  327    ->  warning('"~w" does not exist', [S1]),
  328        fail
  329    ;   Files = Files0
  330    ).
  331
  332file_name_to_atom(Spec, File) :-
  333    atomic(Spec),
  334    !,
  335    atom_string(File, Spec).
  336file_name_to_atom(Spec, File) :-
  337    phrase(segments(Spec), L),
  338    atomic_list_concat(L, /, File).
  339
  340segments(Var) -->
  341    { var(Var),
  342      !,
  343      instantiation_error(Var)
  344    }.
  345segments(A/B) -->
  346    !,
  347    segments(A),
  348    segments(B).
  349segments(A) -->
  350    { must_be(atomic, A) },
  351    [ A ].
 warning(+Fmt, +Args:list) is det
  355warning(Fmt, Args) :-
  356    print_message(warning, format(Fmt, Args)).
  357
  358:- multifile prolog:message//1.  359
  360prolog:message(shell(file_set(Files))) -->
  361    { catch(tty_size(_, Width), _, Width = 80)
  362    },
  363    table(Files, Width).
  364prolog:message(shell(directory(Path))) -->
  365    { dir_name(Path, Name) },
  366    [ '~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
  379table(List, Width) -->
  380    { table_layout(List, Width, Layout),
  381      compound_name_arguments(Array, a, List)
  382    },
  383    table(0, Array, Layout).
  384
  385table(I, Array, Layout) -->
  386    { Cols = Layout.cols,
  387      Index is I // Cols + (I mod Cols) * Layout.rows + 1,
  388      (   (I+1) mod Cols =:= 0
  389      ->  NL = true
  390      ;   NL = false
  391      )
  392    },
  393    (   { arg(Index, Array, Item) }
  394    ->  table_cell(Item, Layout.col_width, NL)
  395    ;   []
  396    ),
  397    (   { I2 is I+1,
  398          I2 < Cols*Layout.rows
  399        }
  400    ->  (   { NL == true }
  401        ->  [ nl ]
  402        ;   []
  403        ),
  404        table(I2, Array, Layout)
  405    ;   []
  406    ).
  407
  408table_cell(Item, ColWidth, false) -->
  409    { label_length(Item, Len),
  410      Spaces is ColWidth - Len
  411    },
  412    table_cell_value(Item),
  413    [ '~|~t~*+'-[Spaces] ].
  414table_cell(Item, _ColWidth, true) -->
  415    table_cell_value(Item).
  416
  417table_cell_value(dir(_, Label)) ==>
  418    [ '~w'-[Label] ].
  419table_cell_value(file(File, Label)) ==>
  420    (   { file_style(File, Style) }
  421    ->  (   { Style == url }
  422        ->  [ url(File,Label) ]
  423        ;   [ ansi(Style, '~w', [Label]) ]
  424        )
  425    ;   [ '~w'-[Label] ]
  426    ).
 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.
  436file_style(File, url) :-
  437    file_name_extension(_, Ext, File),
  438    link_file_extension(Ext),
  439    !.
  440
  441link_file_extension(Ext) :-
  442    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.
  449table_layout(Atoms, Width, _{cols:Cols, rows:Rows, col_width:ColWidth}) :-
  450    length(Atoms, L),
  451    longest(Atoms, Longest),
  452    Cols is max(1, Width // (Longest + 3)),
  453    Rows is integer(L / Cols + 0.49999),    % should be ceil/1
  454    ColWidth is Width // Cols.
  455
  456longest(List, Longest) :-
  457    longest(List, 0, Longest).
  458
  459longest([], M, M) :- !.
  460longest([H|T], Sofar, M) :-
  461    label_length(H, L),
  462    L >= Sofar,
  463    !,
  464    longest(T, L, M).
  465longest([_|T], S, M) :-
  466    longest(T, S, M).
  467
  468label_length(dir(_, Label), Len) =>
  469    atom_length(Label, Len).
  470label_length(file(_, Label), Len) =>
  471    atom_length(Label, Len).
 cls
CLear Screen. Emits ANSI control characters to clear the terminal.
  477cls :-
  478    format(user_error, '\e[3J\e[H\e[2J', []),
  479    format(user_error, '\e[3J\r', [])