change platform::mkdir() to create entire path like "mkdir -p"

update the documentation of the shell command to current state
and specifically explain the difference between built-in and
external commands and correct where built in commands were described
as calling external commands.
This commit is contained in:
Axel Kohlmeyer
2022-03-17 11:34:50 -04:00
parent b7ea33332f
commit 9dfb099197
5 changed files with 86 additions and 46 deletions

View File

@ -20,7 +20,7 @@ Syntax
dir1,dir2 = one or more directories to create
*mv* args = old new
old = old filename
new = new filename
new = new filename or destination folder
*rm* args = file1 file2 ...
file1,file2 = one or more filenames to delete
*rmdir* args = dir1 dir2 ...
@ -36,8 +36,8 @@ Examples
shell cd sub1
shell cd ..
shell mkdir tmp1 tmp2 tmp3
shell rmdir tmp1
shell mkdir tmp1 tmp2/tmp3
shell rmdir tmp1 tmp2
shell mv log.lammps hold/log.1
shell rm TMP/file1 TMP/file2
shell putenv LAMMPS_POTENTIALS=../../potentials
@ -50,34 +50,39 @@ Description
Execute a shell command. A few simple file-based shell commands are
supported directly, in Unix-style syntax. Any command not listed
above is passed as-is to the C-library system() call, which invokes
the command in a shell.
the command in a shell. To use the external executable instead of
the built-in version one needs to use a full path, for example
*/bin/rm* instead of *rm*. The built-in commands will also work
on operating systems, that do not - by default - provide the
corresponding external executables (like *mkdir* on Windows).
This is means to invoke other commands from your input script. For
example, you can move files around in preparation for the next section
of the input script. Or you can run a program that pre-processes data
for input into LAMMPS. Or you can run a program that post-processes
LAMMPS output data.
This command provides a ways to invoke custom commands or executables
from your input script. For example, you can move files around in
preparation for the next section of the input script. Or you can run a
program that pre-processes data for input into LAMMPS. Or you can run a
program that post-processes LAMMPS output data.
With the exception of *cd*, all commands, including ones invoked via a
system() call, are executed by only a single processor, so that
files/directories are not being manipulated by multiple processors.
files/directories are not being manipulated by multiple processors
concurrently which may result in unexpected errors or corrupted files.
The *cd* command executes the Unix "cd" command to change the working
directory. All subsequent LAMMPS commands that read/write files will
use the new directory. All processors execute this command.
The *cd* command changes the current working directory similar to
the ``cd`` command. All subsequent LAMMPS commands that read/write files
will use the new directory. All processors execute this command.
The *mkdir* command executes the Unix "mkdir" command to create one or
more directories.
The *mkdir* command creates directories similar to the Unix ``mkdir -p``
command. That is, it will attempt to create the entire path of
sub-directories if they do not exist yet.
The *mv* command executes the Unix "mv" command to rename a file and/or
move it to a new directory.
The *mv* command renames a file and/or moves it to a new directory.
It cannot rename files across filesystem boundaries or between drives.
The *rm* command executes the Unix "rm" command to remove one or more
files.
The *rm* command deletes file similar to the Unix ``rm`` command.
The *rmdir* command executes the Unix "rmdir" command to remove one or
more directories. A directory must be empty to be successfully
removed.
The *rmdir* command deletes directories similar to Unix ``rmdir`` command.
If a directory is not empty, its contents are also removed recursively
similar to the Unix ``rm -r`` command.
The *putenv* command defines or updates an environment variable directly.
Since this command does not pass through the shell, no shell variable
@ -107,9 +112,9 @@ with 3 arguments: file1 10 file2.
Restrictions
""""""""""""
LAMMPS does not detect errors or print warnings when any of these
commands execute. E.g. if the specified directory does not exist,
executing the *cd* command will silently do nothing.
LAMMPS will do a best effort to detect errors and print suitable
warnings, but due to the nature of delegating commands to the C-library
system() call, this is not always reliable.
Related commands
""""""""""""""""

View File

@ -1194,7 +1194,10 @@ void Input::shell()
if (narg < 2) error->all(FLERR,"Illegal shell mkdir command");
if (me == 0) {
for (int i = 1; i < narg; i++) {
if (platform::mkdir(arg[i]) < 0)
rv = (platform::mkdir(arg[i]) < 0) ? errno : 0;
MPI_Reduce(&rv,&err,1,MPI_INT,MPI_MAX,0,world);
errno = err;
if (err != 0)
error->warning(FLERR, "Shell command 'mkdir {}' failed with error '{}'",
arg[i],utils::getsyserror());
}
@ -1202,11 +1205,16 @@ void Input::shell()
} else if (strcmp(arg[0],"mv") == 0) {
if (narg != 3) error->all(FLERR,"Illegal shell mv command");
if (me == 0) {
if (platform::path_is_directory(arg[2])) {
if (system(fmt::format("mv {} {}", arg[1], arg[2]).c_str()))
error->warning(FLERR,"Shell command 'mv {} {}' returned with non-zero status", arg[1], arg[2]);
} else {
if (rename(arg[1],arg[2]) < 0) {
error->warning(FLERR, "Shell command 'mv {} {}' failed with error '{}'",
arg[1],arg[2],utils::getsyserror());
}
}
}
} else if (strcmp(arg[0],"rm") == 0) {
if (narg < 2) error->all(FLERR,"Illegal shell rm command");
if (me == 0) {
@ -1237,23 +1245,20 @@ void Input::shell()
error->warning(FLERR, "Shell command 'putenv {}' failed with error '{}'",
arg[i], utils::getsyserror());
}
// use work string to concat args back into one string separated by spaces
// invoke string in shell via system()
// concat arguments and invoke string in shell via system()
} else {
int n = 0;
for (int i = 0; i < narg; i++) n += strlen(arg[i]) + 1;
if (n > maxwork) reallocate(work,maxwork,n);
strcpy(work,arg[0]);
if (me == 0) {
std::string cmd = arg[0];
for (int i = 1; i < narg; i++) {
strcat(work," ");
strcat(work,arg[i]);
cmd += " ";
cmd += arg[i];
}
if (me == 0)
if (system(work) != 0)
error->warning(FLERR,"Shell command returned with non-zero status");
if (system(cmd.c_str()) != 0)
error->warning(FLERR,"Shell command {} returned with non-zero status", cmd);
}
}
}

View File

@ -21,6 +21,7 @@
#include <mpi.h>
#include <exception>
#include <deque>
////////////////////////////////////////////////////////////////////////
// include system headers and tweak system settings
@ -752,16 +753,31 @@ int platform::chdir(const std::string &path)
}
/* ----------------------------------------------------------------------
Create a directory
Create a directory. Create entire path if necessary.
------------------------------------------------------------------------- */
int platform::mkdir(const std::string &path)
{
std::deque<std::string> dirlist = { path };
std::string dirname = path_dirname(path);
while ((dirname != ".") && (dirname != "")) {
dirlist.push_front(dirname);
dirname = path_dirname(dirname);
}
int rv;
for (const auto &dir : dirlist) {
if (!path_is_directory(dir)) {
#if defined(_WIN32)
return ::_mkdir(path.c_str());
rv = ::_mkdir(dir.c_str());
#else
return ::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP);
rv = ::mkdir(dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP);
#endif
if (rv != 0) return rv;
}
}
return 0;
}
/* ----------------------------------------------------------------------

View File

@ -271,6 +271,11 @@ namespace platform {
int chdir(const std::string &path);
/*! Create a directory
*
* Unlike the the ``mkdir()`` or ``_mkdir()`` functions of the
* C library, this function will also try to create non-existing sub-directories
* in case they don't exist, and thus behave like the ``mkdir -p`` command rather
* than plain ``mkdir`` or ``md`.
*
* \param path directory path
* \return -1 if unsuccessful, otherwise >= 0 */
@ -279,7 +284,7 @@ namespace platform {
/*! Delete a directory
*
* Unlike the the ``rmdir()`` or ``_rmdir()`` function of the
* Unlike the the ``rmdir()`` or ``_rmdir()`` functions of the
* C library, this function will check for the contents of the
* folder and recurse into any sub-folders, if necessary and
* delete all contained folders and their contents before

View File

@ -323,6 +323,15 @@ TEST(Platform, path_and_directory)
ASSERT_EQ(dirs.size(), 3);
platform::rmdir("path_is_directory");
ASSERT_FALSE(platform::path_is_directory("path_is_directory"));
#if defined(_WIN32)
ASSERT_EQ(platform::mkdir("path_is_directory\\path_is_directory"),0);
ASSERT_TRUE(platform::path_is_directory("path_is_directory\\path_is_directory"));
#else
ASSERT_EQ(platform::mkdir("path_is_directory/path_is_directory"),0);
ASSERT_TRUE(platform::path_is_directory("path_is_directory/path_is_directory"));
#endif
platform::rmdir("path_is_directory");
}
TEST(Platform, get_change_directory)