hsshellscript-3.1.0: src/HsShellScript.hs
-- HsShellScript main module
module HsShellScript (
-- * Command Line Arguments
-- Command line arguments are handled by the module "HsShellScript.Args", which is reexported by "HsShellScript".
module HsShellScript.Args,
-- * Paths and Directories
mkdir, rmdir, pwd, cd, realpath, realpath_s, path_exists, path_exists', is_file, is_dir, with_wd,
-- ** Parsing and Composing Paths
module HsShellScript.Paths,
-- * Symbolic Links
is_symlink, symlink, readlink, readlink',
-- * Manipulating Files
rm, chmod, chown, cp, mv,
HsShellScript.Commands.rename, rename_mv, force_rename, force_mv, force_rename_mv, force_cmd, force_writeable, force_writeable2,
getFileStatus', fileAccess', setFileMode',
-- * Interfaces to Some Specific External Commands
mt_status, fdupes, du,
-- * Calling External Programs
-- ** Running a Subroutine in a Separate Process
-- $subr
-- ** About the @exec@ Functions
-- $exec
-- ** Functions for Forking Child Processes and Calling External Programs
subproc,
spawn,
runprog, RunError(..), show_runerror, to_ioe, as_ioe,
exec, execp, exece, execpe,
echo, silently,
system_runprog, system_throw, execute_file,
child,
explain_processstatus,
call, run,
-- * Redirecting Input and Output
(->-), (->>-), (=>-), (=>>-), (-<-),
(-&>-), (-&>>-),
err_to_out, out_to_err,
-- * Pipes
-- ** File Descriptors in Pipes
-- $fdpipes
-- ** Pipe Creation Functions
(-|-), (=|-), (-|=), (=|=),
redirect,
pipe_to, h_pipe_to,
pipe_from, lazy_pipe_from, h_pipe_from,
pipe_from2, lazy_pipe_from2, h_pipe_from2,
pipes,
-- * Shell-like Quoting
module HsShellScript.Shell,
-- * Creating temporary files and directories
tmp_file, tmp_dir, temp_file, temp_dir, temp_path, with_tmp_file, with_tmp_dir, with_temp_file, with_temp_dir,
-- * Reading mount information
Mntent(..), read_mounts, read_mtab, read_fstab,
-- * Output to the standard stream, colorful logging and error reporting
outm, outm_, logm, logm_, errm, errm_,
isatty,
-- * Miscellaneous
zeros, chomp, lazy_contents, contents, glob,
-- * Error Handling
mainwrapper, errno,
strerror,
perror',
perror,
{-abort, _exit,-}
HsShellScript.ProcErr.failIO,
exitcode,
throwErrno',
show_ioerror,
fill_in_filename, fill_in_location, add_location
)
where
-- import IO
-- import List
-- import Maybe
-- import Monad
-- import Random
-- import System
import Control.Exception
import GHC.IO
import HsShellScript.Args
import HsShellScript.Commands
import HsShellScript.Misc
import HsShellScript.Paths
import HsShellScript.ProcErr
import HsShellScript.Shell
import Prelude hiding (catch)
import System.Console.GetOpt
import System.Directory
import System.Exit
import System.Posix
{- | Error reporting wrapper for the @main@ function. This catches any
HsShellScript generated exceptions, and @IOError@s, prints
an error message and exits with @exitFailure@. The @main@ function
typically looks like this:
>main = mainwrapper $ do ...
The exceptions caught are 'ArgError', 'RunError', 'ProcessStatus' and @IOError@.
-}
mainwrapper :: IO a -- ^ Should be @main@
-> IO a -- ^ Wrapped @main@
mainwrapper io =
io
`catches` [ Handler $ \(argerror :: ArgError) ->
do errm (argerror_message argerror)
putStrLn $ "\n" ++ (argerror_usageinfo argerror)
exitFailure
, Handler $ \(processstatus :: ProcessStatus) ->
do errm $ "Process error. process status = " ++ show ( processstatus :: ProcessStatus )
exitFailure
, Handler $ \(runerror :: RunError) ->
do errm (show_runerror runerror)
exitFailure
, Handler $ \(ioe :: IOError) ->
do errm (show_ioerror ioe)
exitFailure
]
{- $fdpipes
#fdpipes#
With HsShellScript, you build pipes from IO actions, which can replace
themselves with an external program via a variant of @exec@. It's mostly
transparent whether some part of the pipe is a subroutine of the main
program, or an external program.
But actually, there are two cases. When the forked process is a subroutine,
the child's @stdin@ handle is connected to the parent. On the other hand,
when the forked process consists of calling an @exec@ variant, that program's
file descriptor 0 is to be connected to the parent process.
Normally, @stdin@ connects exactly to file descriptor 0, but this isn't
necessarily the case. For instance, when @stdin@ has been closed, the file
descriptor will be reused on the next occasion. When it is reopened again
by calling @GHC.Handle.hDuplicateTo h stdin@, then the new @stdin@
will be using a different file descriptor, and file descriptor 0 will be in
use by another handle. Thus, when forking a subroutine, we're connected via
@stdin@, but we can't expect to be connected via file descriptor 0.
In case the child process is to be replaced with another program, we need to
make sure that right file descriptor connects to the parent process. This is
accomplished by the @exec@ functions. They replace the standard file
descriptors with the ones that the standard handles currently use. See
"HsShellScript#exec" for details.
These two examples work as expected.
Example 1:
>-- This closes stdin.
>c <- contents "-"
>
>pipe_to something
> ( -- execp arranges for "something" to go to foo's file descriptor 0
> execp "foo" []
>
> -|- (do -- Read foo's standard output from new stdin handle
> c' <- lazy_contents "-"
> ...
> )
> )
Example 2:
>-- Call wc to count the number of lines in txt
>count <- fmap (read . chomp) $
> pipe_from (putStr txt -|= execp "wc" ["-l"])
-}
{- $subr
#subr#
It can by very useful to fork a child process, which executes a subroutine of
the main program. In the following example, paths are piped to the @recode@
program in order to convert them from ISO 8859-1 to UTF-8. Its output is read
by a subroutine of the main program, which can use it to rename the files.
>main = mainwrapper $ do
> paths <- contents "-"
> pipe_to paths $
> ( execp "recode" ["-f", "latin1..utf8"]
> -|= (do paths_utf8 <- lazy_contents "-"
> mapM_ (\(path, path_utf8) ->
> ...
> )
> (zip (lines paths) (lines paths_utf8))
> )
> )
The same could be achieved this way:
>main = mainwrapper $ do
> paths <- contents "-"
> paths_utf8 <-
> pipe_from ( putStr paths
> -|= execp "recode" ["-f", "latin1..utf8"]
> )
> mapM_ (\(path, path_utf8) ->
> ...
> )
> (zip (lines paths) (lines paths_utf8))
Most of the time, it's intuitive. But sometimes, the forked subroutine
interferes with the parent process.
When the process clones itself by calling @fork(2)@, everything gets
duplicated - open files, database connections, window system connections...
This becomes an issue when the child process uses any of it. For instance,
any buffered, not yet written data associated with a file handle gets
duplicated. When the child process uses that handle, that data gets written
twice.
The functions which fork a child process ('call', 'spawn', 'silently',
'pipe_to' etc.) flush @stdout@ and @stderr@ (should be unbuffered) before the
fork. So the child process can use them. The pipe functions also take care of
@stdin@, which is used to read from the pipe. But they don't know about any
other handles.
What happens when the subroutine finishes? The control flow would escape into
the main program, doing unexpected things. Therefore the functions which fork
an IO action terminate the child process when the subroutine finishes. They
do so by calling '_exit', circumventing normal program shutdown. Normal
shutdown would flush cloned file handles, shut down database connections now
shared with the parent process etc. Only the @stdout@ and @stderr@ are
flushed before. If the child process requires any more cleanup on
termination, such as flushing new file handles created in the child process,
it's the responsibility of the programmer to do so before the subroutine
exits.
When the subroutine throws an exception, the control flow isn't allowed to
escape into the main program either. Any exception is caught, an error
message is printed, and the child process is terminated with @_exit 1@.
The subroutine /must not/ terminate the child process normally, by calling
@exitWith@ or @exitFailure@. It should terminate with '_exit'. Don't forget
to flush @stdout@ before, which won't be line buffered when not connected to
a terminal. It can also just leave the subroutine. The functions which fork
child processes intercept any attempt of normal program shutdown in the child
process (it's an @ExitException@, see the GHC library documentation). A
warning message is printed, and the child is terminated with @_exit@, with
the same exit code which it would have been.
-}
{- $exec
#exec#
There are five @exec@ variants: 'exec', 'execp', 'exece', 'execpe' and
'execute_file'. The first four are frontends to @execute_file@. They
differ in whether the @PATH@ is searched, and in whether a new environment is
installed. The latter is a replacement for
@System.Posix.Process.executeFile@. They are designed to work intuitively in
conjunction with the functions which fork a child process, such as 'run',
'call', 'spawn', 'pipe_to' etc.
Before replacing the process, @stdout@ and @stderr@ are flushed, so no yet
unwritten data is lost. Then the file descriptors of the process are prepared
for the exec, such that everything works as expected. The standard file
descriptors 0-2 are made to correspond to the standard handles again (this
might have changed, see "HsShellScript#exec"). They are also reset to
blocking mode. All others are closed when the exec succeeds.
/You can't use/ @executeFile@ /directly, unless you take care of the things
outlined at/ "HsShellScript#exec" /and/ 'execute_file' /by yourself./
If replacing the process fails (for instance, because the program wasn't
found), then everything is restored to original state, and an @IOError@ is
thrown, and the process continues with normal error handling. Normally, the
@exec@ functions are used in conjunction with some of the functions which
fork a child process. They also handle errors, so the forked action doesn't
need to cope with failure of @exec@. The error handling and
termination is done via the 'child' function.
Sometimes you want to pass an open file descriptor to the program. In this
case, you can't use the @exec@ variants. You need to call @executeFile@
directly, and take care of the outlined matters by yourself. In this
case, take a look at the source code of @execute_file@.
For full details, see the documentation of 'execute_file'.
-}