-- | -- Module : Streamly.System.Command -- Copyright : (c) 2023 Composewell Technologies -- License : Apache-2.0 -- Maintainer : streamly@composewell.com -- Stability : experimental -- Portability : GHC -- -- Use command strings to execute OS processes. These processes can be used -- just like native Haskell functions - to generate, transform or consume -- streams. It provides a powerful way to write high-level Haskell scripts to -- perform tasks similar to shell scripts without requiring the shell. -- Moreover, the Haskell scripts provide C-like performance. -- -- This module is a wrapper over the "Streamly.System.Process" module. -- -- See also: "Streamly.Internal.System.Command". -- module Streamly.System.Command ( -- * Setup -- | To execute the code examples provided in this module in ghci, please -- run the following commands first. -- -- $setup -- * Overview -- $overview -- * Types ProcessFailure (..) -- * Generation , toBytes , toChunks , toChars , toLines -- * Effects , toString , toStdout , toNull -- * Transformation , pipeBytes , pipeChars , pipeChunks -- -- * Helpers -- , runWith -- , streamWith -- , pipeWith ) where import Streamly.Internal.System.Command import Streamly.Internal.System.Process (ProcessFailure (..)) -- Keep it synced with the Internal module -- $setup -- >>> :set -XFlexibleContexts -- >>> :set -XQuasiQuotes -- >>> import Data.Char (toUpper) -- >>> import Data.Function ((&)) -- >>> import Streamly.Unicode.String (str) -- >>> import qualified Streamly.Data.Array as Array -- >>> import qualified Streamly.Console.Stdio as Stdio -- >>> import qualified Streamly.Data.Fold as Fold -- >>> import qualified Streamly.Data.Stream.Prelude as Stream -- >>> import qualified Streamly.System.Command as Command -- >>> import qualified Streamly.Unicode.Stream as Unicode -- -- >>> import qualified Streamly.Internal.System.Process as Process -- >>> import qualified Streamly.Internal.Console.Stdio as Stdio -- >>> import qualified Streamly.Internal.FileSystem.Dir as Dir -- Note: Commands are not executed using shell -- -- You can use this module to execute system commands and compose them with -- Haskell. It does not use the system shell to execute commands, they are -- executed as independent processes. This provides a convenient and powerful -- way to replace shell scripting with Haskell. Instead of composing commands -- using shell you can use Haskell Streamly streaming APIs to compose them with -- better efficiency and type safety. -- -- Normally, you should not need the system shell but if you want to use shell -- scripts in your program then you can take a look at the @streamly-shell@ -- package which provides convenient wrapper over "Streamly.System.Process" to -- execute shell scripts, commands. -- $overview -- -- Please see the "Streamly.System.Process" for basics. -- -- "Streamly.System.Process" module requires specifying the command executable -- name and its arguments separately (e.g. "ls" "-al") whereas using this -- module we can specify the executable and its arguments more conveniently as -- a single command string e.g. we can execute "ls -al". -- -- A command string is parsed in the same way as a posix shell would parse it. -- A command string consists of whitespace separated tokens with the first -- token treated as the executable name and the rest as arguments. Whitespace -- can be escaped using @\\@. Alternatively, double quotes or single quotes can -- be used to enclose tokens with whitespaces. Quotes can be escaped using @\\@. -- Single quotes inside double quotes or vice-versa are treated as normal -- characters. -- -- You can use the string quasiquoter 'Streamly.Unicode.String.str' to write -- commands conveniently, it allows Haskell variable expansion as well e.g.: -- -- >>> f = "file name" -- >>> [str|ls -al "#{f} with spaces"|] -- "ls -al \"file name with spaces\"" -- -- With the "Streamly.System.Command" module you can write the examples in the -- "Streamly.System.Process" module more conveniently. -- -- = Executables as functions -- -- The shell command @echo "hello world" | tr [a-z] [A-Z]@ can be written as -- follows using this module: -- -- >>> :{ -- Command.toBytes [str|echo "hello world"|] -- & Command.pipeBytes [str|tr [a-z] [A-Z]|] -- & Stream.fold Stdio.write -- :} -- HELLO WORLD -- -- = Shell commands as functions -- -- If you want to execute the same command using the shell: -- -- >>> :{ -- Command.toBytes [str|sh "-c" "echo 'hello world' | tr [a-z] [A-Z]"|] -- & Stream.fold Stdio.write -- :} -- HELLO WORLD -- -- = Running Commands Concurrently -- -- Running @grep@ concurrently on many files: -- -- >>> :{ -- grep file = -- Command.toBytes [str|grep -H "pattern" #{file}|] -- & Stream.handle (\(_ :: Command.ProcessFailure) -> Stream.nil) -- & Stream.foldMany (Fold.takeEndBy (== 10) Array.write) -- :} -- -- >>> :{ -- pgrep = -- Dir.readFiles "." -- & Stream.parConcatMap id grep -- & Stream.fold Stdio.writeChunks -- :} --