diff --git a/NEWS b/NEWS index 808930f5..12761fed 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,12 @@ lnav v0.8.1: * Added ":hide-lines-before", ":hide-lines-after", and ":show-lines-before-and-after" commands so that you can filter out log lines based on time. + * Scripts containing lnav commands/queries can now be executed using + the pipe ('|') hotkey. See the documentation for more information. + * Added an ":eval" command that can be used to execute a command or + query after performing environment variable substitution. + * Added an ":echo" command that can be useful for scripts to message + the user. Interface Changes: * The 'o/O' hotkeys have been reassigned to navigate through log diff --git a/docs/source/commands.rst b/docs/source/commands.rst index 290d57e2..662f95d7 100644 --- a/docs/source/commands.rst +++ b/docs/source/commands.rst @@ -100,3 +100,15 @@ Output shell command and open the output in lnav. * pipe-line-to - Pipe the top line in the current view to a shell command and open the output in lnav. + + +Miscellaneous +------------- + +* echo [-n] - Display the given message in the command prompt. Useful + for scripts to display messages to the user. The '-n' option leaves out the + new line at the end of the message. +* eval - Evaluate the given command or SQL query after performing + environment variable substitution. The argument to *eval* must start with a + colon, semi-colon, or pipe character to signify whether the argument is a + command, SQL query, or a script to be executed, respectively. diff --git a/docs/source/hotkeys.rst b/docs/source/hotkeys.rst index 68c420f8..a94f59ac 100644 --- a/docs/source/hotkeys.rst +++ b/docs/source/hotkeys.rst @@ -117,6 +117,7 @@ Spatial Navigation - Next/previous bookmark * - |ks| o |ke| - |ks| Shift |ke| + |ks| o |ke| + - - Forward/backward through log messages with a matching "opid" field * - |ks| y |ke| - |ks| Shift |ke| + |ks| y |ke| @@ -223,5 +224,7 @@ Query - Execute an SQL query * - |ks| : |ke| - Execute an internal command, see :ref:`commands` for more information + * - |ks| \| |ke| + - Execute an lnav script located in a format directory. * - |ks| Ctrl |ke| + |ks| ] |ke| - Abort a diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9550c9c4..3ba2048d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ set(diag_STAT_SRCS session_data.cc sequence_matcher.cc shared_buffer.cc + shlex.cc sqlite-extension-func.c statusview_curses.cc string-extension-functions.cc @@ -111,6 +112,7 @@ set(diag_STAT_SRCS readline_possibilities.hh relative_time.hh sequence_sink.hh + shlex.hh status_controllers.hh strong_int.hh sysclip.hh diff --git a/src/Makefile.am b/src/Makefile.am index 567c7d12..54aa957a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -160,6 +160,7 @@ noinst_HEADERS = \ sequence_sink.hh \ session_data.hh \ shared_buffer.hh \ + shlex.hh \ sql_util.hh \ sqlite-extension-func.h \ status_controllers.hh \ @@ -244,6 +245,7 @@ libdiag_a_SOURCES = \ session_data.cc \ sequence_matcher.cc \ shared_buffer.cc \ + shlex.cc \ sqlite-extension-func.c \ statusview_curses.cc \ string-extension-functions.cc \ diff --git a/src/command_executor.cc b/src/command_executor.cc index 855f634b..f7e03ee5 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -29,11 +29,14 @@ #include "config.h" +#include + #include #include "json_ptr.hh" #include "pcrecpp.h" #include "lnav.hh" +#include "log_format_loader.hh" #include "command_executor.hh" @@ -71,7 +74,7 @@ static int sql_progress(const struct log_cursor &lc) string execute_from_file(const string &path, int line_number, char mode, const string &cmdline); -string execute_command(string cmdline) +string execute_command(const string &cmdline) { stringstream ss(cmdline); @@ -99,7 +102,7 @@ string execute_command(string cmdline) return msg; } -string execute_sql(string sql, string &alt_msg) +string execute_sql(const string &sql, string &alt_msg) { db_label_source &dls = lnav_data.ld_db_row_source; auto_mem stmt(sqlite3_finalize); @@ -150,9 +153,16 @@ string execute_sql(string sql, string &alt_msg) name = sqlite3_bind_parameter_name(stmt.in(), lpc + 1); if (name[0] == '$') { + map &vars = lnav_data.ld_local_vars.top(); + map::iterator local_var; const char *env_value; - if ((env_value = getenv(&name[1])) != NULL) { + if ((local_var = vars.find(&name[1])) != vars.end()) { + sqlite3_bind_text(stmt.in(), lpc + 1, + local_var->second.c_str(), -1, + SQLITE_TRANSIENT); + } + else if ((env_value = getenv(&name[1])) != NULL) { sqlite3_bind_text(stmt.in(), lpc + 1, env_value, -1, SQLITE_STATIC); } } @@ -246,15 +256,16 @@ string execute_sql(string sql, string &alt_msg) return retval; } -void execute_file(string path, bool multiline) +static string execute_file_contents(const string &path, bool multiline) { + string retval; FILE *file; if (path == "-") { file = stdin; } else if ((file = fopen(path.c_str(), "r")) == NULL) { - return; + return "error: unable to open file"; } int line_number = 0, starting_line_number = 0; @@ -263,7 +274,9 @@ void execute_file(string path, bool multiline) ssize_t line_size; string cmdline; char mode = '\0'; + pair dir_and_base = split_path(path); + lnav_data.ld_path_stack.push(dir_and_base.first); while ((line_size = getline(&line, &line_max_size, file)) != -1) { line_number += 1; @@ -280,7 +293,7 @@ void execute_file(string path, bool multiline) case ';': case '|': if (mode) { - execute_from_file(path, starting_line_number, mode, trim(cmdline)); + retval = execute_from_file(path, starting_line_number, mode, trim(cmdline)); } starting_line_number = line_number; @@ -292,7 +305,7 @@ void execute_file(string path, bool multiline) cmdline += line; } else { - execute_from_file(path, line_number, ':', line); + retval = execute_from_file(path, line_number, ':', line); } break; } @@ -300,12 +313,84 @@ void execute_file(string path, bool multiline) } if (mode) { - execute_from_file(path, starting_line_number, mode, trim(cmdline)); + retval = execute_from_file(path, starting_line_number, mode, trim(cmdline)); } if (file != stdin) { fclose(file); } + lnav_data.ld_path_stack.pop(); + + return retval; +} + +string execute_file(const string &path_and_args, bool multiline) +{ + map > scripts; + map >::iterator iter; + static_root_mem wordmem; + string msg, retval; + + log_info("Executing file: %s", path_and_args.c_str()); + + int exp_rc = wordexp(path_and_args.c_str(), + wordmem.inout(), + WRDE_NOCMD | WRDE_UNDEF); + + if (!wordexperr(exp_rc, msg)) { + retval = msg; + } + else if (wordmem->we_wordc == 0) { + retval = "error: no script specified"; + } + else { + lnav_data.ld_local_vars.push(map()); + + string script_name = wordmem->we_wordv[0]; + map &vars = lnav_data.ld_local_vars.top(); + char env_arg_name[32]; + string result; + + snprintf(env_arg_name, sizeof(env_arg_name), "%d", (int) wordmem->we_wordc - 1); + + vars["#"] = env_arg_name; + for (int lpc = 0; lpc < wordmem->we_wordc; lpc++) { + snprintf(env_arg_name, sizeof(env_arg_name), "%d", lpc); + vars[env_arg_name] = wordmem->we_wordv[lpc]; + } + + vector paths_to_exec; + + find_format_scripts(lnav_data.ld_config_paths, scripts); + if ((iter = scripts.find(script_name)) != scripts.end()) { + paths_to_exec = iter->second; + } + if (access(script_name.c_str(), R_OK) == 0) { + paths_to_exec.push_back(script_name); + } + else { + string local_path = lnav_data.ld_path_stack.top() + "/" + script_name; + + if (access(local_path.c_str(), R_OK) == 0) { + paths_to_exec.push_back(local_path); + } + } + + if (!paths_to_exec.empty()) { + for (vector::iterator path_iter = paths_to_exec.begin(); + path_iter != paths_to_exec.end(); + ++path_iter) { + result = execute_file_contents(*path_iter, multiline); + } + retval = "Executed: " + script_name + " -- " + result; + } + else { + retval = "error: unknown script -- " + script_name; + } + lnav_data.ld_local_vars.pop(); + } + + return retval; } string execute_from_file(const string &path, int line_number, char mode, const string &cmdline) @@ -322,7 +407,7 @@ string execute_from_file(const string &path, int line_number, char mode, const s retval = execute_sql(cmdline, alt_msg); break; case '|': - execute_file(cmdline); + retval = execute_file(cmdline); break; default: retval = execute_command(cmdline); @@ -362,7 +447,7 @@ void execute_init_commands(vector > &msgs) msg = execute_sql(iter->substr(1), alt_msg); break; case '|': - execute_file(iter->substr(1)); + msg = execute_file(iter->substr(1)); break; } diff --git a/src/command_executor.hh b/src/command_executor.hh index 175326f3..71ac904b 100644 --- a/src/command_executor.hh +++ b/src/command_executor.hh @@ -34,10 +34,10 @@ #include -std::string execute_command(std::string cmdline); +std::string execute_command(const std::string &cmdline); -std::string execute_sql(std::string sql, std::string &alt_msg); -void execute_file(std::string path, bool multiline = true); +std::string execute_sql(const std::string &sql, std::string &alt_msg); +std::string execute_file(const std::string &path_and_args, bool multiline = true); void execute_init_commands(std::vector > &msgs); int sql_callback(sqlite3_stmt *stmt); diff --git a/src/fs-extension-functions.cc b/src/fs-extension-functions.cc index 08ff20e1..67eab9ed 100644 --- a/src/fs-extension-functions.cc +++ b/src/fs-extension-functions.cc @@ -115,12 +115,9 @@ static void sql_dirname(sqlite3_context *context, text_end -= 1; } - if (text_end == -1) { - sqlite3_result_text(context, - path_in[0] == '/' ? "/" : ".", 1, - SQLITE_STATIC); - return; - } + sqlite3_result_text(context, + path_in[0] == '/' ? "/" : ".", 1, + SQLITE_STATIC); } static void sql_joinpath(sqlite3_context *context, diff --git a/src/help.txt b/src/help.txt index 49171a4b..ad602a19 100644 --- a/src/help.txt +++ b/src/help.txt @@ -316,7 +316,16 @@ through the file. that can be used in queries. See the SQL section below for more information. - CTRL+] Abort command-line entry started with '/', ':', or ';'. + |