-1

I'm trying to use echo inside a Puppet rule to add a line to .bashrc, but I can't seem to get the quoting right.

'/usr/bin/echo -E PS1=\"[\t--------------------------------------------------------\n-\u@\h:\W]\$\" >> /home/unu/.bashrc'

This thing gives me the following result:

PS1="[t--------------------------------------------------------n-u@h:W]$"

Another attempt:

'/usr/bin/echo -E PS1="[\t--------------------------------------------------------\n-\u@\h:\W]\$" >> /home/unu/.bashrc'

This thing gives me the following:

PS1=[\t--------------------------------------------------------\n-\u@\h:\W]$

Another one:

'/usr/bin/echo -e PS1="[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$" >> /home/unu/.bashrc'

This one is giving me this:

PS1=[   --------------------------------------------------------
-\u@\h:\W]$

I can't seem to find a way to do this without having \ or " interpreted in some way. How should I do this?

I thought of using more quotes, but it causes a syntax error from Puppet:

"/usr/bin/echo -e 'PS1="[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$"' >> /home/unu/.bashrc"

Gets this result:

Error: Could not retrieve catalog from remote server: Error 500 on SERVER: Server Error: Syntax error at '' (file: /etc/puppetlabs/code/environments/production/modules/profile/manifests/ps1.pp, line: 3, column: 38) on node centoslave1

This is the whole code:

class profile::ps1 {
        exec { 'myps1':
        command => "/usr/bin/echo -e 'PS1="[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$"' >> /home/unu/.bashrc"
        }
}
ilkkachu
  • 138,973
iamAguest
  • 483

4 Answers4

2

What is going on

command => "/usr/bin/echo -e 'PS1="[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$"' >> /home/unu/.bashrc"

Understanding where this is going wrong is the clue to getting it right. The right-hand side of the element, after the =>, must be a string. Strings have several forms in Puppet. A double-quoted string starts with double quotation marks and ends at second set. So your string is

"/usr/bin/echo -e 'PS1="
You then follow it with gibberish that is not syntactically correct for a manifest:
[\t--------------------------------------------------------\n-\u@\h:\W]\$
and another string:
"' >> /home/unu/.bashrc"

Doing this properly

Build the correct setting from the bottom up. Start with what you want the command to output to that file. The output is a shell command that sets the PS1 shell variable to:

[\t--------------------------------------------------------\n-\u@\h:\W]\$
Such a command is, using single quoting (preferable to double quoting here because it does not require escaping for those \ and $ characters) to stop the shell from unescaping the escape sequences:
PS1='[\t--------------------------------------------------------\n-\u@\h:\W]\$'
You can output such a command with echo invoked by a shell (a point that we will return to), but you need to ensure, with more escaping, that the single quotes reach the echo command:
echo PS1=\''[\t--------------------------------------------------------\n-\u@\h:\W]\$'\'

This however has problems with the fact that echo does not have consistent behaviour across platforms and shells, and (ironically) its possible conversion of escape sequences is actually not what you want. Indeed, despite the tag on your question, the shell running the command need not be the Bourne Again shell, depending from the operating system (which you have not specified). It could be the Debian Almquist shell, for example. For better results, use printf:

printf "PS1='%s'\n" '[\t--------------------------------------------------------\n-\u@\h:\W]\$' >> /home/unu/.bashrc

Observe that the assumption has been to this point that it is a command-line given to a shell for execution. The escaping and quoting of the arguments passed to echo and printf, so that they end up with the right contents, has been according to shell rules. So take note of the question comment that talks about providers.

Now you need to encode that in a Puppet string. Single-quoted strings are again the better choice here, as you only need to escape the ' and \ characters (which is not quite the same as the shell rules for single-quoted strings, note):

command => 'printf "PS1=\'%s\'\\n" \'[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$\' >> /home/unu/.bashrc'

Further reading

JdeBP
  • 68,745
  • Just one question: does echo or printf also interpret this stuff inside a variable? So if I make a variable before that being $myps1thing='PS1="sadfasdgasg"' then just do command => "printf ${myps1thing} >> /home/unu/.bashrc", will that also die? – iamAguest Sep 21 '18 at 06:52
  • Also your last example Is still outputting a broken piece of text: PS1='PS1="[\t--------------------------------------------------------\n-\u@\h:\W]\$"' – iamAguest Sep 21 '18 at 07:20
  • I got it to work by modifying your command => 'printf "PS1=\'%s\'\\n" \'PS1="[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$"\' >> /home/unu/.bashrc' and removing parts of it, and I got command => 'printf "PS1=\'%s\'\\n" "[\\t--------------------------------------------------------\\n-\\u@\\h:\\W]\\$" >> /home/unu/.bashrc' – iamAguest Sep 21 '18 at 07:24
1

There are (at least) two levels of things eating up your characters:

  1. the shell
  2. echo itself

If you run:

echo "hello, \"world\""

you get the output

hello, "world"

because the shell ate the double-quotes at backslashes, not echo. The shell took them as shell syntax to mean pass hello, "world" to echo as a single argument. Which echo then printed, plus a newline.

echo -e especially also interprets backslash sequences (depending on the implementation); the simple solution to this problem is to use printf '%s\n' WHATEVER instead of echo WHATEVER.

For the most part, you can protect something from the shell with single-quotes (as long as it doesn't contain single-quotes itself). So, you might want:

printf '%s\n' 'PS1="[\t--------------------------------------------------------\n-\u@\h:\W]\$"'

which gives:

PS1="[\t--------------------------------------------------------\n-\u@\h:\W]\$"

If you have to survive multiple layers of shell-escaping, you might find printf '%q\n' WHATEVER useful, which prints it in a format to survive the shell (but this might be a Bash-only feature, haven't checked).

BTW: If you need to protect a single-quote inside a single-quoted string, you have to do something like '\'':

$ printf '%s\n' 'i'\''ll'
i'll

which if you squint at it carefully is actually 'i' + \' + 'll'.

PS: PS1 undergoes another expansion (see the Bash docs); you know about the ones like \u obviously. But variables are expanded too — so if you had something after the $ at the end, you'd need to escape it (with three backslashes if inside a double-quoted string): PS1="foo\\\$PATH % "

derobert
  • 109,670
  • Note that if they want the \$ prompt escape in the final PS1, the final assignment to it must escape the backslash, or use single quotes. i.e. PS1='[...]\$' or PS1="[...]\\$" (you could add another backslash to escape the dollar sign, but I don't think it's required at the end of the double-quoted string) – ilkkachu Sep 20 '18 at 16:46
  • @ilkkachu PS1="foo\$" works here... It's at the end, so it doesn't get expanded. But that is a good thing to note for OP. Added. – derobert Sep 20 '18 at 19:55
  • what I mean is that setting PS1='$' is different from setting PS1='\$'. The latter expands to a # if the effective UID of the process is 0. If they only use that prompt in a non-root user's shell, and it doesn't get inherited anywhere, it obviously doesn't matter. But it's worth noting that final double-quoted string that ends up in .bashrc may also need some escaping. So, one set of escapes for the shell that runs the echo, another for the shell that runs the resulting assignment... – ilkkachu Sep 20 '18 at 22:36
  • It'd be easier with single quotes on both levels, like printf "PS1='%s'\n" '[...]\$'. (If they can use single quotes in that Puppet rule, that is.) – ilkkachu Sep 20 '18 at 22:40
0

Use printf:

printf '%s\n' 'PS1="[\t--------------------------------------------------------\n-\u@\h:\W]\$"' >> /home/unu/.bashrc
pLumo
  • 22,565
0

You could simply use cat:

cat /var/tmp/ps1-file >> /home/unu/.bashrc

The ps1-file can be created manually (using your preferred editor) so you avoid the escaping issues in bash/echo/printf. For that file to reach the node, you'll need to add a file section.

file { "/var/tmp/ps1-file"
    source => 'puppet:///modules/bashrc/ps1'
}

This means putting the original file under modulepath. As its contents are a small amount of text, you could specify them with content =>, but that would have quoting/escaping issues of its own.

JigglyNaga
  • 7,886
  • I tried writing the whole thing to a file and then using cat, but the mere process of writing it to a file is screwing it up... I'm not talking about manually writing it, but writing it to a file through echo or printf – iamAguest Sep 21 '18 at 07:15
  • So stop usingecho or printf -- you don't need them to copy bytes from one file to the end or another. – JigglyNaga Sep 21 '18 at 08:13
  • But I do need them to write to a file. I'm doing this remotely, how could I write the text to a file if not with echo or printf? There isn't any premade file, that would defeat the purpose of it... – iamAguest Sep 21 '18 at 08:23
  • Edit the file, using whatever editor you like. Or cat > file, type the line you need, then Ctrl-d. – JigglyNaga Sep 21 '18 at 08:42