fish: <! cmd executes a shell command and inlines the resulting commands.

The new guestfish construct "<! cmd" executes the shell command
"cmd", and then anything printed to stdout by "cmd" is parsed
and executed as a guestfish command.

This allows some very hairy shell scripting with guestfish.
This commit is contained in:
Richard W.M. Jones
2011-01-18 11:46:03 +00:00
parent 61a4db138e
commit c3887285ab
2 changed files with 83 additions and 0 deletions

View File

@@ -61,6 +61,7 @@ static void shell_script (void);
static void script (int prompt);
static void cmdline (char *argv[], int optind, int argc);
static struct parsed_command parse_command_line (char *buf, int *exit_on_error_rtn);
static int execute_and_inline (const char *cmd, int exit_on_error);
static void initialize_readline (void);
static void cleanup_readline (void);
#ifdef HAVE_LIBREADLINE
@@ -708,6 +709,18 @@ parse_command_line (char *buf, int *exit_on_error_rtn)
return pcmd;
}
/* If the next two characters are "<!" then pass the command to
* popen(3), read the result and execute it as guestfish commands.
*/
if (buf[0] == '<' && buf[1] == '!') {
int r = execute_and_inline (&buf[2], *exit_on_error_rtn);
if (r == -1)
pcmd.status = -1;
else
pcmd.status = 0;
return pcmd;
}
/* If the next character is '-' allow the command to fail without
* exiting on error (just for this one command though).
*/
@@ -823,6 +836,50 @@ parse_command_line (char *buf, int *exit_on_error_rtn)
return pcmd;
}
/* Used to handle "<!" (execute command and inline result). */
static int
execute_and_inline (const char *cmd, int global_exit_on_error)
{
FILE *pp;
char *line = NULL;
size_t len = 0;
ssize_t n;
int exit_on_error;
struct parsed_command pcmd;
pp = popen (cmd, "r");
if (!pp) {
perror ("popen");
return -1;
}
while ((n = getline (&line, &len, pp)) != -1) {
exit_on_error = global_exit_on_error;
/* Chomp final line ending which parse_command_line would not expect. */
if (n > 0 && line[n-1] == '\n')
line[n-1] = '\0';
pcmd = parse_command_line (line, &exit_on_error);
if (pcmd.status == -1 && exit_on_error)
exit (EXIT_FAILURE);
if (pcmd.status == 1) {
if (issue_command (pcmd.cmd, pcmd.argv, pcmd.pipe, exit_on_error) == -1) {
if (exit_on_error) exit (EXIT_FAILURE);
}
}
}
free (line);
if (pclose (pp) == -1) {
perror ("pclose");
return -1;
}
return 0;
}
static void
cmdline (char *argv[], int optind, int argc)
{

View File

@@ -676,6 +676,32 @@ C<local/remote-data.tar.gz>. (See C<tgz-out>).
To change the local directory, use the C<lcd> command. C<!cd> will
have no effect, due to the way that subprocesses work in Unix.
=head2 LOCAL COMMANDS WITH INLINE EXECUTION
If a line starts with I<E<lt>!> then the shell command is executed (as
for I<!>), but subsequently any output (stdout) of the shell command
is parsed and executed as guestfish commands.
Thus you can use shell script to construct arbitrary guestfish
commands which are then parsed by guestfish.
For example it is tedious to create a sequence of files
(eg. C</foo.1> through C</foo.100>) using guestfish commands
alone. However this is simple if we use a shell script to
create the guestfish commands for us:
<! for n in `seq 1 100`; do echo write /foo.$n $n; done
or with names like C</foo.001>:
<! for n in `seq 1 100`; do printf "write /foo.%03d %d\n" $n $n; done
When using guestfish interactively it can be helpful to just run the
shell script first (ie. remove the initial C<E<lt>> character so it is
just an ordinary I<!> local command), see what guestfish commands it
would run, and when you are happy with those prepend the C<E<lt>>
character to run the guestfish commands for real.
=head1 PIPES
Use C<command E<lt>spaceE<gt> | command> to pipe the output of the