diff --git a/doc_src/string.txt b/doc_src/string.txt index 6d6b7c908..02f4bd074 100644 --- a/doc_src/string.txt +++ b/doc_src/string.txt @@ -4,6 +4,7 @@ \fish{synopsis} string escape [(-n | --no-quoted)] [--style=xxx] [STRING...] string join [(-q | --quiet)] SEP [STRING...] +string join0 [(-q | --quiet)] [STRING...] string length [(-q | --quiet)] [STRING...] string lower [(-q | --quiet)] [STRING...] string match [(-a | --all)] [(-e | --entire)] [(-i | --ignore-case)] [(-r | --regex)] @@ -51,6 +52,10 @@ The third is `--style=url` which ensures the string can be used as a URL by hex `string join` joins its STRING arguments into a single string separated by SEP, which can be an empty string. Exit status: 0 if at least one join was performed, or 1 otherwise. +\subsection string-join0 "join0" subcommand + +`string join` joins its STRING arguments into a single string separated by the zero byte (NUL), and adds a trailing NUL. This is most useful in conjunction with tools that accept NUL-delimited input, such as `sort -z`. Exit status: 0 if at least one join was performed, or 1 otherwise. + \subsection string-length "length" subcommand `string length` reports the length of each string argument in characters. Exit status: 0 if at least one non-empty STRING was given, or 1 otherwise. @@ -248,11 +253,18 @@ foo2 0xBadC0de \endfish -\subsection string-example-split0 Split0 Examples +\subsection string-example-split0 NUL Delimited Examples \fish{cli-dark} -# Count files in a directory, without being confused by newlines. +>_ # Count files in a directory, without being confused by newlines. >_ count (find . -print0 | string split0) +42 + +>_ # Sort a list of elements which may contain newlines +>_ set foo beta alpha\ngamma +>_ set foo (string join0 $foo | sort -z | string split0) +>_ string escape $foo[1] +alpha\ngamma \endfish \subsection string-example-replace-literal Replace Literal Examples diff --git a/src/builtin_string.cpp b/src/builtin_string.cpp index 113738e61..01f079517 100644 --- a/src/builtin_string.cpp +++ b/src/builtin_string.cpp @@ -542,14 +542,15 @@ static int string_unescape(parser_t &parser, io_streams_t &streams, int argc, wc DIE("should never reach this statement"); } -static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { +static int string_join_maybe0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv, + bool is_join0) { options_t opts; opts.quiet_valid = true; int optind; - int retval = parse_opts(&opts, &optind, 1, argc, argv, parser, streams); + int retval = parse_opts(&opts, &optind, is_join0 ? 0 : 1, argc, argv, parser, streams); if (retval != STATUS_CMD_OK) return retval; - const wchar_t *sep = opts.arg1; + const wcstring sep = is_join0 ? wcstring(1, L'\0') : wcstring(opts.arg1); int nargs = 0; arg_iterator_t aiter(argv, optind, streams); while (const wcstring *arg = aiter.nextstr()) { @@ -562,12 +563,20 @@ static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_ nargs++; } if (nargs > 0 && !opts.quiet) { - streams.out.push_back(L'\n'); + streams.out.push_back(is_join0 ? L'\0' : L'\n'); } return nargs > 1 ? STATUS_CMD_OK : STATUS_CMD_ERROR; } +static int string_join(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { + return string_join_maybe0(parser, streams, argc, argv, false /* is_join0 */); +} + +static int string_join0(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { + return string_join_maybe0(parser, streams, argc, argv, true /* is_join0 */); +} + static int string_length(parser_t &parser, io_streams_t &streams, int argc, wchar_t **argv) { options_t opts; opts.quiet_valid = true; @@ -1271,13 +1280,12 @@ static const struct string_subcommand { wchar_t **argv); //!OCLINT(unused param) } -string_subcommands[] = {{L"escape", &string_escape}, {L"join", &string_join}, - {L"length", &string_length}, {L"match", &string_match}, - {L"replace", &string_replace}, {L"split", &string_split}, - {L"split0", &string_split0}, {L"sub", &string_sub}, - {L"trim", &string_trim}, {L"lower", &string_lower}, - {L"upper", &string_upper}, {L"repeat", &string_repeat}, - {L"unescape", &string_unescape}, {NULL, NULL}}; +string_subcommands[] = { + {L"escape", &string_escape}, {L"join", &string_join}, {L"join0", &string_join0}, + {L"length", &string_length}, {L"match", &string_match}, {L"replace", &string_replace}, + {L"split", &string_split}, {L"split0", &string_split0}, {L"sub", &string_sub}, + {L"trim", &string_trim}, {L"lower", &string_lower}, {L"upper", &string_upper}, + {L"repeat", &string_repeat}, {L"unescape", &string_unescape}, {NULL, NULL}}; /// The string builtin, for manipulating strings. int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) { diff --git a/tests/string.err b/tests/string.err index c7cd024fc..aebb82052 100644 --- a/tests/string.err +++ b/tests/string.err @@ -298,5 +298,8 @@ string repeat -l fakearg #################### # string split0 +#################### +# string join0 + #################### # string split0 in functions diff --git a/tests/string.in b/tests/string.in index 60dcaaf01..e28f98bab 100644 --- a/tests/string.in +++ b/tests/string.in @@ -348,6 +348,12 @@ count (echo -ne 'abc\x00def\x00ghi' | string split0) count (echo -ne 'abc\ndef\x00ghi\x00' | string split0) count (echo -ne 'abc\ndef\nghi' | string split0) +logmsg string join0 +set tmp beta alpha\ngamma +count (string join \n $tmp) +count (string join0 $tmp) +count (string join0 $tmp | string split0) + logmsg string split0 in functions # This function outputs some newline-separated content, and some # explicitly separated content. diff --git a/tests/string.out b/tests/string.out index bded690e7..5fee2fbe1 100644 --- a/tests/string.out +++ b/tests/string.out @@ -443,6 +443,12 @@ a\x00g 2 1 +#################### +# string join0 +3 +2 +2 + #################### # string split0 in functions 4