mirror of https://github.com/tstack/lnav.git
[cmds] add :redirect-to command for redirecting the output of commands in scripts
Related to #551
This commit is contained in:
parent
ea5ac46c0e
commit
ac7ae1275a
4
NEWS
4
NEWS
|
@ -3,6 +3,10 @@ lnav v0.8.5:
|
|||
Features:
|
||||
* The ":write-*" commands will now accept "/dev/clipboard" as a file name
|
||||
that writes to the system clipboard.
|
||||
* Added a ":redirect-to <path>" command to redirect command output to the
|
||||
given file. This command is mostly useful in scripts where one might
|
||||
want to redirect all output from commands like ":echo" and ":write-to -"
|
||||
to a single file.
|
||||
|
||||
Interface Changes:
|
||||
* The auto-complete behavior in the prompt has been modified to fall back
|
||||
|
|
|
@ -143,6 +143,10 @@ Output
|
|||
shell command and open the output in lnav.
|
||||
* pipe-line-to <shell-cmd> - Pipe the top line in the current view to a shell
|
||||
command and open the output in lnav.
|
||||
* redirect-to [path] - If a path is given, all output from commands, like
|
||||
":echo" and when writing to stdout (e.g. :write-to -), will be sent to the
|
||||
given file. If no path is specified, the current redirect will be cleared
|
||||
and output will be captured as it was before the redirect was done.
|
||||
|
||||
.. _misc-cmd:
|
||||
|
||||
|
|
|
@ -385,6 +385,7 @@ static string execute_file_contents(exec_context &ec, const string &path, bool m
|
|||
pair<string, string> dir_and_base = split_path(path);
|
||||
|
||||
ec.ec_path_stack.push_back(dir_and_base.first);
|
||||
ec.ec_output_stack.emplace_back(nonstd::nullopt);
|
||||
while ((line_size = getline(&line, &line_max_size, file)) != -1) {
|
||||
line_number += 1;
|
||||
|
||||
|
@ -431,6 +432,7 @@ static string execute_file_contents(exec_context &ec, const string &path, bool m
|
|||
} else {
|
||||
fclose(file);
|
||||
}
|
||||
ec.ec_output_stack.pop_back();
|
||||
ec.ec_path_stack.pop_back();
|
||||
|
||||
return retval;
|
||||
|
@ -731,7 +733,26 @@ int sql_callback(exec_context &ec, sqlite3_stmt *stmt)
|
|||
|
||||
future<string> pipe_callback(exec_context &ec, const string &cmdline, auto_fd &fd)
|
||||
{
|
||||
if (lnav_data.ld_output_stack.empty()) {
|
||||
auto out = ec.get_output();
|
||||
|
||||
if (out) {
|
||||
FILE *file = *out;
|
||||
|
||||
return std::async(std::launch::async, [&fd, file]() {
|
||||
char buffer[1024];
|
||||
ssize_t rc;
|
||||
|
||||
if (file == stdout) {
|
||||
lnav_data.ld_stdout_used = true;
|
||||
}
|
||||
|
||||
while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
|
||||
fwrite(buffer, rc, 1, file);
|
||||
}
|
||||
|
||||
return string();
|
||||
});
|
||||
} else {
|
||||
auto pp = make_shared<piper_proc>(fd, false);
|
||||
static int exec_count = 0;
|
||||
char desc[128];
|
||||
|
@ -755,22 +776,6 @@ future<string> pipe_callback(exec_context &ec, const string &cmdline, auto_fd &f
|
|||
task();
|
||||
|
||||
return task.get_future();
|
||||
} else {
|
||||
return std::async(std::launch::async, [&]() {
|
||||
FILE *file = lnav_data.ld_output_stack.top();
|
||||
char buffer[1024];
|
||||
ssize_t rc;
|
||||
|
||||
if (file == stdout) {
|
||||
lnav_data.ld_stdout_used = true;
|
||||
}
|
||||
|
||||
while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
|
||||
fwrite(buffer, rc, 1, file);
|
||||
}
|
||||
|
||||
return string();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <future>
|
||||
#include <string>
|
||||
|
||||
#include "optional.hpp"
|
||||
#include "auto_fd.hh"
|
||||
#include "attr_line.hh"
|
||||
#include "textview_curses.hh"
|
||||
|
@ -47,9 +48,9 @@ typedef std::future<std::string> (*pipe_callback_t)(
|
|||
exec_context &ec, const std::string &cmdline, auto_fd &fd);
|
||||
|
||||
struct exec_context {
|
||||
exec_context(std::vector<logline_value> *line_values = NULL,
|
||||
sql_callback_t sql_callback = NULL,
|
||||
pipe_callback_t pipe_callback = NULL)
|
||||
exec_context(std::vector<logline_value> *line_values = nullptr,
|
||||
sql_callback_t sql_callback = nullptr,
|
||||
pipe_callback_t pipe_callback = nullptr)
|
||||
: ec_top_line(vis_line_t(0)),
|
||||
ec_dry_run(false),
|
||||
ec_line_values(line_values),
|
||||
|
@ -58,6 +59,7 @@ struct exec_context {
|
|||
this->ec_local_vars.push(std::map<std::string, std::string>());
|
||||
this->ec_path_stack.emplace_back(".");
|
||||
this->ec_source.emplace("unknown", 0);
|
||||
this->ec_output_stack.emplace_back(nonstd::nullopt);
|
||||
}
|
||||
|
||||
std::string get_error_prefix() {
|
||||
|
@ -70,6 +72,18 @@ struct exec_context {
|
|||
return "error:" + source.first + ":" + std::to_string(source.second) + ":";
|
||||
}
|
||||
|
||||
nonstd::optional<FILE *> get_output() {
|
||||
for (auto iter = this->ec_output_stack.rbegin();
|
||||
iter != this->ec_output_stack.rend();
|
||||
++iter) {
|
||||
if (*iter) {
|
||||
return *iter;
|
||||
}
|
||||
}
|
||||
|
||||
return nonstd::nullopt;
|
||||
}
|
||||
|
||||
vis_line_t ec_top_line;
|
||||
bool ec_dry_run;
|
||||
|
||||
|
@ -79,6 +93,7 @@ struct exec_context {
|
|||
std::map<std::string, std::string> ec_global_vars;
|
||||
std::vector<std::string> ec_path_stack;
|
||||
std::stack<std::pair<std::string, int>> ec_source;
|
||||
std::vector<nonstd::optional<FILE *>> ec_output_stack;
|
||||
|
||||
attr_line_t ec_accumulator;
|
||||
|
||||
|
|
20
src/lnav.cc
20
src/lnav.cc
|
@ -3302,10 +3302,8 @@ int main(int argc, char *argv[])
|
|||
lnav_data.ld_vtab_manager->register_vtab(new log_format_vtab_impl(
|
||||
*log_format::find_root_format("generic_log")));
|
||||
|
||||
for (std::vector<log_format *>::iterator iter = log_format::get_root_formats().begin();
|
||||
iter != log_format::get_root_formats().end();
|
||||
++iter) {
|
||||
log_vtab_impl *lvi = (*iter)->get_vtab_impl();
|
||||
for (auto &iter : log_format::get_root_formats()) {
|
||||
log_vtab_impl *lvi = iter->get_vtab_impl();
|
||||
|
||||
if (lvi != NULL) {
|
||||
lnav_data.ld_vtab_manager->register_vtab(lvi);
|
||||
|
@ -3481,10 +3479,8 @@ int main(int argc, char *argv[])
|
|||
|
||||
if (lnav_data.ld_flags & LNF_CHECK_CONFIG) {
|
||||
rescan_files(true);
|
||||
for (auto file_iter = lnav_data.ld_files.begin();
|
||||
file_iter != lnav_data.ld_files.end();
|
||||
++file_iter) {
|
||||
auto lf = (*file_iter);
|
||||
for (auto &ld_file : lnav_data.ld_files) {
|
||||
auto lf = ld_file;
|
||||
|
||||
lf->rebuild_index();
|
||||
|
||||
|
@ -3496,7 +3492,7 @@ int main(int argc, char *argv[])
|
|||
retval = EXIT_FAILURE;
|
||||
continue;
|
||||
}
|
||||
for (logfile::iterator line_iter = lf->begin();
|
||||
for (auto line_iter = lf->begin();
|
||||
line_iter != lf->end();
|
||||
++line_iter) {
|
||||
if (!line_iter->is_continued()) {
|
||||
|
@ -3587,14 +3583,14 @@ int main(int argc, char *argv[])
|
|||
log_info("lnav_data:");
|
||||
log_info(" flags=%x", lnav_data.ld_flags);
|
||||
log_info(" commands:");
|
||||
for (std::list<string>::iterator cmd_iter =
|
||||
for (auto cmd_iter =
|
||||
lnav_data.ld_commands.begin();
|
||||
cmd_iter != lnav_data.ld_commands.end();
|
||||
++cmd_iter) {
|
||||
log_info(" %s", cmd_iter->c_str());
|
||||
}
|
||||
log_info(" files:");
|
||||
for (map<string, logfile_open_options>::iterator file_iter =
|
||||
for (auto file_iter =
|
||||
lnav_data.ld_file_names.begin();
|
||||
file_iter != lnav_data.ld_file_names.end();
|
||||
++file_iter) {
|
||||
|
@ -3608,7 +3604,7 @@ int main(int argc, char *argv[])
|
|||
bool found_error = false;
|
||||
|
||||
init_session();
|
||||
lnav_data.ld_output_stack.push(stdout);
|
||||
lnav_data.ld_exec_context.ec_output_stack.back() = stdout;
|
||||
alerter::singleton().enabled(false);
|
||||
|
||||
log_tc = &lnav_data.ld_views[LNV_LOG];
|
||||
|
|
|
@ -305,8 +305,6 @@ struct _lnav_data {
|
|||
|
||||
relative_time ld_last_relative_time;
|
||||
|
||||
std::stack<FILE *> ld_output_stack;
|
||||
|
||||
std::map<std::string, std::vector<script_metadata> > ld_scripts;
|
||||
|
||||
exec_context ld_exec_context;
|
||||
|
|
|
@ -568,7 +568,9 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
|
|||
toclose = outfile;
|
||||
}
|
||||
else if (split_args[0] == "-" || split_args[0] == "/dev/stdout") {
|
||||
if (lnav_data.ld_output_stack.empty()) {
|
||||
auto ec_out = ec.get_output();
|
||||
|
||||
if (!ec_out) {
|
||||
outfile = stdout;
|
||||
nodelay(lnav_data.ld_window, 0);
|
||||
endwin();
|
||||
|
@ -583,7 +585,7 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
|
|||
"----------------\n\n");
|
||||
}
|
||||
else {
|
||||
outfile = lnav_data.ld_output_stack.top();
|
||||
outfile = *ec_out;
|
||||
}
|
||||
if (outfile == stdout) {
|
||||
lnav_data.ld_stdout_used = true;
|
||||
|
@ -673,7 +675,7 @@ static string com_save_to(exec_context &ec, string cmdline, vector<string> &args
|
|||
|
||||
if ((handle = yajl_gen_alloc(NULL)) == NULL) {
|
||||
if (outfile != stdout) {
|
||||
fclose(outfile);
|
||||
closer(outfile);
|
||||
}
|
||||
return "error: unable to allocate memory";
|
||||
}
|
||||
|
@ -945,6 +947,52 @@ static string com_pipe_to(exec_context &ec, string cmdline, vector<string> &args
|
|||
return retval;
|
||||
}
|
||||
|
||||
static string com_redirect_to(exec_context &ec, string cmdline, vector<string> &args)
|
||||
{
|
||||
if (args.empty()) {
|
||||
args.emplace_back("filename");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (args.size() == 1) {
|
||||
if (ec.ec_dry_run) {
|
||||
return "info: redirect will be cleared";
|
||||
}
|
||||
|
||||
ec.ec_output_stack.back() = nonstd::nullopt;
|
||||
return "info: cleared redirect";
|
||||
}
|
||||
|
||||
string fn = trim(remaining_args(cmdline, args));
|
||||
vector<string> split_args;
|
||||
shlex lexer(fn);
|
||||
scoped_resolver scopes = {
|
||||
&ec.ec_local_vars.top(),
|
||||
&ec.ec_global_vars,
|
||||
};
|
||||
|
||||
if (!lexer.split(split_args, scopes)) {
|
||||
return "error: unable to parse arguments";
|
||||
}
|
||||
if (split_args.size() > 1) {
|
||||
return "error: more than one file name was matched";
|
||||
}
|
||||
|
||||
if (ec.ec_dry_run) {
|
||||
return "info: output will be redirected to -- " + split_args[0];
|
||||
}
|
||||
|
||||
FILE *file = fopen(split_args[0].c_str(), "w");
|
||||
|
||||
if (file == nullptr) {
|
||||
return "error: unable to open file -- " + split_args[0];
|
||||
}
|
||||
|
||||
ec.ec_output_stack.back() = file;
|
||||
|
||||
return "info: redirecting output to file -- " + split_args[0];
|
||||
}
|
||||
|
||||
static string com_highlight(exec_context &ec, string cmdline, vector<string> &args)
|
||||
{
|
||||
string retval = "error: expecting regular expression to highlight";
|
||||
|
@ -2933,14 +2981,15 @@ static string com_echo(exec_context &ec, string cmdline, vector<string> &args)
|
|||
retval = "";
|
||||
}
|
||||
|
||||
auto ec_out = ec.get_output();
|
||||
if (ec.ec_dry_run) {
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_value("The text to output:");
|
||||
lnav_data.ld_preview_source.replace_with(attr_line_t(retval));
|
||||
retval = "";
|
||||
}
|
||||
else if (!lnav_data.ld_output_stack.empty()) {
|
||||
FILE *outfile = lnav_data.ld_output_stack.top();
|
||||
else if (ec_out) {
|
||||
FILE *outfile = *ec_out;
|
||||
|
||||
if (outfile == stdout) {
|
||||
lnav_data.ld_stdout_used = true;
|
||||
|
@ -2951,6 +3000,8 @@ static string com_echo(exec_context &ec, string cmdline, vector<string> &args)
|
|||
putc('\n', outfile);
|
||||
}
|
||||
fflush(outfile);
|
||||
|
||||
retval = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3838,6 +3889,19 @@ readline_context::command_t STD_COMMANDS[] = {
|
|||
.with_tags({"io"})
|
||||
.with_example({"sed -e 's/foo/bar/g'"})
|
||||
},
|
||||
{
|
||||
"redirect-to",
|
||||
com_redirect_to,
|
||||
|
||||
help_text(":redirect-to")
|
||||
.with_summary("Redirect the output of commands to the given file")
|
||||
.with_parameter(help_text(
|
||||
"path", "The path to the file to write."
|
||||
" If not specified, the current redirect will be cleared")
|
||||
.optional())
|
||||
.with_tags({"io", "scripting"})
|
||||
.with_example({"/tmp/script-output.txt"})
|
||||
},
|
||||
{
|
||||
"enable-filter",
|
||||
com_enable_filter,
|
||||
|
|
|
@ -484,14 +484,14 @@ void rl_callback(void *dummy, readline_curses *rc)
|
|||
time_t current_time = time(NULL);
|
||||
string path_and_args = rc->get_value();
|
||||
|
||||
lnav_data.ld_output_stack.push(tmpout);
|
||||
ec.ec_output_stack.back() = tmpout.in();
|
||||
string result = execute_file(ec, path_and_args);
|
||||
string::size_type lf_index = result.find('\n');
|
||||
if (lf_index != string::npos) {
|
||||
result = result.substr(0, lf_index);
|
||||
}
|
||||
rc->set_value(result);
|
||||
lnav_data.ld_output_stack.pop();
|
||||
ec.ec_output_stack.back() = nonstd::nullopt;
|
||||
|
||||
struct stat st;
|
||||
|
||||
|
@ -506,7 +506,7 @@ void rl_callback(void *dummy, readline_curses *rc)
|
|||
lnav_data.ld_file_names[desc]
|
||||
.with_fd(fd_copy)
|
||||
.with_detect_format(false);
|
||||
lnav_data.ld_files_to_front.push_back(make_pair(desc, 0));
|
||||
lnav_data.ld_files_to_front.emplace_back(desc, 0);
|
||||
|
||||
if (lnav_data.ld_rl_view != NULL) {
|
||||
lnav_data.ld_rl_view->set_alt_value(
|
||||
|
|
|
@ -348,6 +348,8 @@ dist_noinst_DATA = \
|
|||
formats/jsontest3/format.json \
|
||||
formats/nestedjson/format.json \
|
||||
formats/scripts/multiline-echo.lnav \
|
||||
formats/scripts/redirecting.lnav \
|
||||
formats/scripts/nested-redirecting.lnav \
|
||||
formats/sqldir/init.sql \
|
||||
formats/timestamp/format.json \
|
||||
log-samples/sample-27353a72ba4025448f261dcfa6ea16e474187795.txt \
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
:echo HOWDY!
|
||||
:redirect-to hw2.txt
|
||||
:echo HELLO, WORLD!
|
||||
:redirect-to
|
||||
:echo GOODBYE, WORLD!
|
|
@ -0,0 +1,6 @@
|
|||
:echo Howdy!
|
||||
:redirect-to hw.txt
|
||||
:echo Hello, World!
|
||||
|nested-redirecting
|
||||
:redirect-to
|
||||
:echo Goodbye, World!
|
|
@ -16,3 +16,36 @@ check_output "multiline-echo is not working?" <<EOF
|
|||
Hello, World!
|
||||
Goodbye, World!
|
||||
EOF
|
||||
|
||||
run_test ${lnav_test} -n -d /tmp/lnav.err \
|
||||
-I ${test_dir} \
|
||||
-f 'redirecting' \
|
||||
scripts-empty
|
||||
|
||||
check_error_output "redirecting has errors?" <<EOF
|
||||
EOF
|
||||
|
||||
check_output "redirecting is not working?" <<EOF
|
||||
Howdy!
|
||||
Goodbye, World!
|
||||
EOF
|
||||
|
||||
diff -w -u - hw.txt <<EOF
|
||||
Hello, World!
|
||||
HOWDY!
|
||||
GOODBYE, WORLD!
|
||||
EOF
|
||||
|
||||
if test $? -ne 0; then
|
||||
echo "Script output was not redirected?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
diff -w -u - hw2.txt <<EOF
|
||||
HELLO, WORLD!
|
||||
EOF
|
||||
|
||||
if test $? -ne 0; then
|
||||
echo "Script output was not redirected?"
|
||||
exit 1
|
||||
fi
|
||||
|
|
Loading…
Reference in New Issue