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).
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%comspec% (Windows only)$SHELLposix_shell102shell_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).
127cd :- 128 cd(~). 129 130cd(Dir) :- 131 name_to_file(Dir, Name), 132 working_directory(_, Name).
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))).
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 -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)).
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).
285rm(File) :-
286 name_to_file(File, A),
287 delete_file(A).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 ].
372warning(Fmt, Args) :- 373 print_message(warning, format(Fmt, Args)). 374 375:- multifile prolog:message//1. 376 377prologmessage(shell(file_set(Files))) --> 378 { catch(tty_size(_, Width), _, Width = 80) 379 }, 380 table(Files, Width). 381prologmessage(shell(directory(Path))) --> 382 { dir_name(Path, Name) }, 383 [ '~w'-[Name] ].
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 ).
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).
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).
494cls :-
495 format(user_error, '\e[3J\e[H\e[2J', []),
496 format(user_error, '\e[3J\r', [])
Elementary shell commands
This library provides some basic (POSIX) shell commands defined in Prolog, such as
pwdandlsfor 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 frontendswipl-wincan capture process output on all supported platforms.