Support reading to stdout via builtin read -

Added an option to read to stdout via `read -`. While it may seem
useless at first blush, it lets you do things like include

    mysql -p(read --silent) ...

Without needing to save to a local variable and then echo it back.
Kicks in when `-` is provided as the variable name to read to. This is
in keeping with the de facto syntax for reading/writing from/to
stdin/stdout instead of a file in, e.g., tar, cat, and other standard
unix utilities.
This commit is contained in:
Mahmoud Al-Qudsi 2017-09-16 13:50:47 -05:00
parent 15bdf6fa00
commit 06afcb43b4

View file

@ -2,6 +2,7 @@
#include "config.h" // IWYU pragma: keep #include "config.h" // IWYU pragma: keep
#include <errno.h> #include <errno.h>
#include <iostream>
#include <limits.h> #include <limits.h>
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
@ -374,6 +375,10 @@ static int validate_read_args(const wchar_t *cmd, read_cmd_opts_t &opts, int arg
// Verify all variable names. // Verify all variable names.
for (int i = 0; i < argc; i++) { for (int i = 0; i < argc; i++) {
if (!valid_var_name(argv[i])) { if (!valid_var_name(argv[i])) {
if (wcscmp(L"-", argv[i]) == 0) {
//writing to stdout instead
continue;
}
streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, argv[i]); streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, argv[i]);
builtin_print_help(parser, streams, cmd, streams.err); builtin_print_help(parser, streams, cmd, streams.err);
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
@ -383,6 +388,26 @@ static int validate_read_args(const wchar_t *cmd, read_cmd_opts_t &opts, int arg
return STATUS_CMD_OK; return STATUS_CMD_OK;
} }
void print_or_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) {
if (key == L"-") {
for (const auto &val : vals) {
std::wcout << val << std::endl;
}
}
else {
env_set(key, mode, vals);
}
}
void print_or_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) {
if (key == L"-") {
std::wcout << val << std::endl;
}
else {
env_set_one(key, mode, val);
}
}
/// The read builtin. Reads from stdin and stores the values in environment variables. /// The read builtin. Reads from stdin and stores the values in environment variables.
int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) { int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
wchar_t *cmd = argv[0]; wchar_t *cmd = argv[0];
@ -455,7 +480,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (opts.array) { if (opts.array) {
// Array mode: assign each char as a separate element of the sole var. // Array mode: assign each char as a separate element of the sole var.
env_set(argv[0], opts.place, chars); print_or_set(argv[0], opts.place, chars);
} else { } else {
// Not array mode: assign each char to a separate var with the remainder being assigned // Not array mode: assign each char to a separate var with the remainder being assigned
// to the last var. // to the last var.
@ -463,16 +488,16 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
size_t j = 0; size_t j = 0;
for (; i + 1 < argc; ++i) { for (; i + 1 < argc; ++i) {
if (j < chars.size()) { if (j < chars.size()) {
env_set_one(argv[i], opts.place, chars[j]); print_or_set_one(argv[i], opts.place, chars[j]);
j++; j++;
} else { } else {
env_set_one(argv[i], opts.place, L""); print_or_set_one(argv[i], opts.place, L"");
} }
} }
if (i < argc) { if (i < argc) {
wcstring val = chars.size() == static_cast<size_t>(argc) ? chars[i] : L""; wcstring val = chars.size() == static_cast<size_t>(argc) ? chars[i] : L"";
env_set_one(argv[i], opts.place, val); print_or_set_one(argv[i], opts.place, val);
} else { } else {
env_set_empty(argv[i], opts.place); env_set_empty(argv[i], opts.place);
} }
@ -490,13 +515,13 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
loc.first != wcstring::npos; loc = wcstring_tok(buff, opts.delimiter, loc)) { loc.first != wcstring::npos; loc = wcstring_tok(buff, opts.delimiter, loc)) {
tokens.emplace_back(wcstring(buff, loc.first, loc.second)); tokens.emplace_back(wcstring(buff, loc.first, loc.second));
} }
env_set(argv[0], opts.place, tokens); print_or_set(argv[0], opts.place, tokens);
} else { } else {
// We're using a delimiter provided by the user so use the `string split` behavior. // We're using a delimiter provided by the user so use the `string split` behavior.
wcstring_list_t splits; wcstring_list_t splits;
split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(), split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(),
&splits, LONG_MAX); &splits, LONG_MAX);
env_set(argv[0], opts.place, splits); print_or_set(argv[0], opts.place, splits);
} }
} else { } else {
// Not array mode. Split the input into tokens and assign each to the vars in sequence. // Not array mode. Split the input into tokens and assign each to the vars in sequence.
@ -510,7 +535,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (loc.first != wcstring::npos) { if (loc.first != wcstring::npos) {
substr = wcstring(buff, loc.first, loc.second); substr = wcstring(buff, loc.first, loc.second);
} }
env_set_one(argv[i], opts.place, substr); print_or_set_one(argv[i], opts.place, substr);
} }
} else { } else {
// We're using a delimiter provided by the user so use the `string split` behavior. // We're using a delimiter provided by the user so use the `string split` behavior.
@ -520,7 +545,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(), split_about(buff.begin(), buff.end(), opts.delimiter.begin(), opts.delimiter.end(),
&splits, argc - 1); &splits, argc - 1);
for (size_t i = 0; i < (size_t)argc && i < splits.size(); i++) { for (size_t i = 0; i < (size_t)argc && i < splits.size(); i++) {
env_set_one(argv[i], opts.place, splits[i]); print_or_set_one(argv[i], opts.place, splits[i]);
} }
} }
} }