Write Text to File Without Redirection, Pipe, or Function – Command Guide

quotingshell-scriptyad

Pipes and redirection are two of the most powerful functions in Linux, and I love them.

However, I'm stuck with a situation where I need to write a fixed piece of text to a file without using a pipe, redirection or a function.

I'm using Bash in case that makes a difference.

First: Why?

I'll explain why, in case there's a simpler solution.

I have a background yad notification with some menu entries. In some of the menu entries, I want the notification to write a fixed piece of text to a file. Here's an example of what I mean.

yad --notification --command=quit --menu='Example!echo sample >text.txt'

The problem is that yad doesn't accept redirection, so it literally prints the string sample >text.txt instead of redirecting.

Likewise, the pipe symbol (|) is a separator in yad; but if you change that, yad takes it as a literal character. For example:

yad --notification --command=quit --separator='#' --menu='Example!echo sample | tee text.txt'

This literally prints the string sample | tee text.txt instead of piping.

There's also no point in writing a function for yad to call, because yad runs in its own space and doesn't recognise the function.

Hence my question

Thus, I want a command like echo, cat or printf that takes an output file as an argument rather than a redirect. I have searched for such a command but cannot find it.

I can, of course, write my own and put it in the default path:

FILENAME="${1}"
shift
printf '%s\n' "${*}" >"${FILENAME}"

and then

yad --notification --command=quit --menu='Example!myscript text.txt sample'

But, I'll be surprised indeed if Linux doesn't already have something like this!

Thank you

Best Answer

This is a bit of an XY problem but fortunately you've explained your real problem so it's possible to give a meaningful answer.

Sure, there are commands that can write text to a file without relying on their environment to open the file. For example, sh can do that: pass it the arguments -c and echo text >filename.

Note that this does meet the requirement of “without redirection” here, since the output of sh is not redirected anywhere. There's a redirection inside the program that sh runs, but that's ok, it's just an internal detail of how sh works.

But does this solve your actual problem? Your actual problem is to write text to a file from a yad action. In order to resolve that, you need to determine what a yad action is. Unfortunately, the manual does not document this. All it says is

menu:STRING

Set popup menu for notification icon. STRING must be in form name1[!action1[!icon1]]|name2[!action2[!icon2]].... Empty name add separator to menu. Separator character for values (e.g. |) sets with --separator argument. Separator character for menu items (e.g. !) sets with --item-separator argument.

The action is a string, but a Unix command is a list of strings: a command name and its arguments. There are several plausible ways to turn a string into a command name and its arguments, including:

  • Treating the string as a command name and calling it with no arguments. Since echo foo prints foo, rather than attempting to execute the program echo foo, this is not what yad does.
  • Passing the string to a shell. Since echo >filename prints >filename, rather than writing to filename, this is not what yad does.
  • Some custom string splitting. At this point, this is presumably what yad does, but depending on exactly how it does it, the solution to your problem can be different.

Looking at the source code, the action is passed to popup_menu_item_activate_cb which calls the Glib function g_spawn_command_line_async. This function splits the given string using g_shell_parse_argv which has the following behavior, which is almost never what is desirable but can be worked around:

Parses a command line into an argument vector, in much the same way the shell would, but without many of the expansions the shell would perform (variable expansion, globs, operators, filename expansion, etc. are not supported). The results are defined to be the same as those you would get from a UNIX98 /bin/sh, as long as the input contains none of the unsupported shell expansions. If the input does contain such expansions, they are passed through literally.

So you can run a shell command by prefixing it with sh -c ' and terminating with '. If you need a single quote inside the shell command, write '\''. Alternatively, you can run a shell command by prefixing it with sh -c ", terminating with ", and adding a backslash before any of the characters "$\` that appear in the command. Take care of the nested quoting since the action is itself quoted in the shell script that calls yad.

yad --notification \
  --menu='Simple example!sh -c "echo sample text >text.txt"' \
  --menu='Single-double-single quotes!sh -c "echo '\''Here you can put everything except single quotes literally: two  spaces, a $dollar and a single'\''\'\'''\''quote.'\'' >text.txt"' \
  --menu="Double-single-double quotes!sh -c 'echo \"Here punctuation is a bit tricky: two  spaces, a \\\$dollar and a single'\\''quote.\"' >text.txt'"
Related Question