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).
shell%comspec% (Windows only)$SHELLposix_shellThe shell's exit status is not our business, so shell/0 succeeds whatever it is.
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).
116cd :- 117 cd(~). 118 119cd(Dir) :- 120 name_to_file(Dir, Name), 121 working_directory(_, Name).
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))).
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).
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)).
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).
268rm(File) :-
269 name_to_file(File, A),
270 delete_file(A).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 ].
355warning(Fmt, Args) :- 356 print_message(warning, format(Fmt, Args)). 357 358:- multifile prolog:message//1. 359 360prologmessage(shell(file_set(Files))) --> 361 { catch(tty_size(_, Width), _, Width = 80) 362 }, 363 table(Files, Width). 364prologmessage(shell(directory(Path))) --> 365 { dir_name(Path, Name) }, 366 [ '~w'-[Name] ].
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 ).
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).
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).
477cls :-
478 format(user_error, '\e[3J\e[H\e[2J', []),
479 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.