18

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

9
  • 1
    Why not: yad --notification --command=quit --menu='Example!echo sample' > text?
    – jesse_b
    Commented Oct 20, 2020 at 13:41
  • @jesse_b, there are multiple menu entries with different requirements. But, I like your idea and I'll play with it to see if I can make that work, thank you. Commented Oct 20, 2020 at 13:43
  • 3
    Pipes and redirections are features of the shell, and nothing to do with the commands that you run in it such as yad Commented Oct 20, 2020 at 13:57
  • 2
    dd of=myName status=none copies stdin to the named file without invoking any additional syntax or processes. Commented Oct 21, 2020 at 12:15
  • awk 'BEGIN{print "sample text" >"filename"} or to make it work in a single-quoted argument to yad awk -v d="sample text" -v f="filename" "BEGIN{print d >f}" Commented Oct 21, 2020 at 21:39

4 Answers 4

32

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'"
5
  • Wonderfully explained, Gilles, thank you! After a lot of experimentation (including the fact that I want to use a pipe for complex reasons), I've found that the solution you describe is the best way forward. Commented Oct 20, 2020 at 19:54
  • 12
    When a program is so poorly documented you have to literally read the source code to figure out how to use it... Commented Oct 21, 2020 at 10:34
  • 6
    @MathematicalOrchid It's called Use the Source, Luke.
    – gerrit
    Commented Oct 21, 2020 at 13:20
  • 2
    @MathematicalOrchid: Reading the source code is still better than not having access to the source code.
    – pts
    Commented Oct 21, 2020 at 21:14
  • @pts Yeah, source code is definitely better than nothing... I would still prefer real documentation though. Commented Oct 22, 2020 at 8:06
5

If you wanna use features of shell, then run a shell:

yad --notification --command=quit --menu='Example!sh -c "echo sample >text.txt"'
4
  • That's pretty cool, albeit inefficient! I'm still toying with the idea from @jesse_b, but I'll mark this as the answer as it answers my question. Commented Oct 20, 2020 at 14:16
  • Does this work in this case, i.e. does yad process the quotes in the command line? I'm asking, since usually that's done by just calling the shell, so it seems odd that yad would instead this part itself instead of just passing the task to the shell and getting redirections etc. in the process...
    – ilkkachu
    Commented Oct 20, 2020 at 15:21
  • @ilkkachu — Yes, this works. Yad takes the string literally without translation, and so passes it to sh correctly. Commented Oct 22, 2020 at 10:30
  • @PaddyLandau, well, except that the string yad gets here is --menu=Example!sh -c "echo sample >text.txt", and what it needs to pass to sh is sh, -c, echo sample >text.txt as three distinct arguments, so for that to work, it must split that string and it must process those quotes.
    – ilkkachu
    Commented Oct 22, 2020 at 11:04
4

A simple custom script will do, e.g. printto.py:

#!/usr/bin/python

import sys

if len(sys.argv) == 3:
    f = open(sys.argv[1], "w")
    f.write(sys.argv[2])
    f.close()
else :
    print("usage: printto.py file message")
    sys.exit(1)
1
  • Thanks. I ended up doing this because of its brevity, albeit in Bash. I'll upvote this because it's a good answer. Commented Oct 21, 2020 at 12:05
0

I am wondering if "yad" will run as a subprocess/subshell within parentheses and whether you can redirect from the subprocess.:

 ( yad BLAH BLAH BLAH ) > output.txt

or

( yad BLAH BLAH BLAH ) >& output.txt
1
  • Thanks for the idea (it was pointed out in one of the comments). It does work in some scenarios, but in others (e.g. using a FIFO), it causes problems. Commented Oct 23, 2020 at 13:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .