4646#include < osquery/utils/conversions/join.h>
4747#include < osquery/utils/conversions/tryto.h>
4848#include < osquery/utils/info/version.h>
49+ #include < osquery/utils/scope_guard.h>
4950
5051#if defined(SQLITE_ENABLE_WHERETRACE)
5152extern int sqlite3WhereTrace;
@@ -72,6 +73,14 @@ SHELL_FLAG(bool, L, false, "List all table names");
7273SHELL_FLAG (string, A, " " , " Select all from a table" );
7374SHELL_FLAG (string, connect, " " , " Connect to an extension socket" );
7475
76+ // / One-shot query execution flags.
77+ SHELL_FLAG (string, query, " " , " Execute a single SQL query and exit" );
78+ SHELL_FLAG (string, query_file, " " , " Execute SQL query from a file and exit" );
79+ SHELL_FLAG (string,
80+ output,
81+ " " ,
82+ " Write results to a file (omit or use '-' for stdout)" );
83+
7584DECLARE_string (nullvalue);
7685DECLARE_string (extensions_socket);
7786DECLARE_string (tls_hostname);
@@ -799,14 +808,16 @@ static void set_table_name(struct callback_data* p, const char* zName) {
799808
800809static void pretty_print_if_needed (struct callback_data * pArg) {
801810 if ((pArg != nullptr ) && pArg->mode == MODE_Pretty) {
811+ FILE* out = (pArg->out != nullptr ) ? pArg->out : stdout;
802812 if (osquery::FLAGS_json_pretty) {
803- osquery::jsonPrettyPrint (pArg->prettyPrint ->results );
813+ osquery::jsonPrettyPrint (pArg->prettyPrint ->results , out );
804814 } else if (osquery::FLAGS_json) {
805- osquery::jsonPrint (pArg->prettyPrint ->results );
815+ osquery::jsonPrint (pArg->prettyPrint ->results , out );
806816 } else {
807817 osquery::prettyPrint (pArg->prettyPrint ->results ,
808818 pArg->prettyPrint ->columns ,
809- pArg->prettyPrint ->lengths );
819+ pArg->prettyPrint ->lengths ,
820+ out);
810821 }
811822 pArg->prettyPrint ->results .clear ();
812823 pArg->prettyPrint ->columns .clear ();
@@ -1740,7 +1751,19 @@ static int process_input(struct callback_data* p, FILE* in) {
17401751
17411752 if (nSql != 0 ) {
17421753 if (_all_whitespace (zSql) == 0 ) {
1743- fprintf (stderr, " Error: incomplete SQL: %s\n " , zSql);
1754+ // At EOF with non-whitespace SQL remaining - try to execute it.
1755+ // This allows piped input like "echo 'select 1' | osqueryd -S" to work
1756+ // without requiring a trailing semicolon.
1757+ p->cnt = 0 ;
1758+ rc = shell_exec (zSql, shell_callback, p, &zErrMsg);
1759+ if (rc != 0 || zErrMsg != nullptr ) {
1760+ if (zErrMsg != nullptr ) {
1761+ fprintf (stderr, " Error: %s\n " , zErrMsg);
1762+ sqlite3_free (zErrMsg);
1763+ zErrMsg = nullptr ;
1764+ }
1765+ errCnt++;
1766+ }
17441767 }
17451768 }
17461769 if (zSql != nullptr ) {
@@ -1833,6 +1856,8 @@ int runPack(struct callback_data* data) {
18331856
18341857int launchIntoShell (int argc, char ** argv) {
18351858 struct callback_data data {};
1859+ auto pretty_print_guard =
1860+ scope_guard::create ([&data]() { delete data.prettyPrint ; });
18361861 main_init (&data);
18371862
18381863#if defined(SQLITE_ENABLE_WHERETRACE)
@@ -1848,6 +1873,26 @@ int launchIntoShell(int argc, char** argv) {
18481873 signal (SIGINT, interrupt_handler);
18491874
18501875 data.out = stdout;
1876+ FILE* output_file = nullptr ;
1877+
1878+ // Handle --output flag: write results to a file instead of stdout.
1879+ // Use "-" to explicitly write to stdout.
1880+ if (!FLAGS_output.empty () && FLAGS_output != " -" ) {
1881+ output_file = fopen (FLAGS_output.c_str (), " w" );
1882+ if (output_file == nullptr ) {
1883+ fprintf (stderr,
1884+ " Error: Cannot open output file '%s'\n " ,
1885+ FLAGS_output.c_str ());
1886+ return 1 ;
1887+ }
1888+ data.out = output_file;
1889+ }
1890+
1891+ auto output_file_guard = scope_guard::create ([&output_file]() {
1892+ if (output_file != nullptr ) {
1893+ fclose (output_file);
1894+ }
1895+ });
18511896
18521897 // Set modes and settings from CLI flags.
18531898 data.showHeader = static_cast <int >(FLAGS_header);
@@ -1882,6 +1927,22 @@ int launchIntoShell(int argc, char** argv) {
18821927 delete[] cmd;
18831928 } else if (!FLAGS_pack.empty ()) {
18841929 rc = runPack (&data);
1930+ } else if (!FLAGS_query.empty ()) {
1931+ // Run a single query from --query flag
1932+ rc = runQuery (&data, FLAGS_query.c_str ());
1933+ } else if (!FLAGS_query_file.empty ()) {
1934+ // Read query from file and execute
1935+ std::string query_content;
1936+ auto status = readFile (FLAGS_query_file, query_content);
1937+ if (!status.ok ()) {
1938+ fprintf (stderr,
1939+ " Error reading query file '%s': %s\n " ,
1940+ FLAGS_query_file.c_str (),
1941+ status.getMessage ().c_str ());
1942+ rc = 1 ;
1943+ } else {
1944+ rc = runQuery (&data, query_content.c_str ());
1945+ }
18851946 } else if (argc > 1 && argv[1 ] != nullptr ) {
18861947 // Run a command or statement from CLI
18871948 char * query = argv[1 ];
@@ -1890,12 +1951,6 @@ int launchIntoShell(int argc, char** argv) {
18901951 rc = (rc == 2 ) ? 0 : rc;
18911952 } else {
18921953 rc = runQuery (&data, query);
1893- if (rc != 0 ) {
1894- if (data.prettyPrint != nullptr ) {
1895- delete data.prettyPrint ;
1896- }
1897- return rc;
1898- }
18991954 }
19001955 } else {
19011956 // Run commands received from standard input
@@ -1926,9 +1981,6 @@ int launchIntoShell(int argc, char** argv) {
19261981
19271982 set_table_name (&data, nullptr );
19281983
1929- if (data.prettyPrint != nullptr ) {
1930- delete data.prettyPrint ;
1931- }
19321984 return rc;
19331985}
19341986} // namespace osquery
0 commit comments