Why does a 'sudo -i' login shell break a here-doc command string argument?
In the sequence of five commands below, all depend on single-quotes to hand off possible variable substitution to the called bash
shell rather than the calling shell. The calling user is xx, but the called shell will be run as user yy. The first command substitutes $HOME with the calling shell's value because the called shell is not a login shell. The second command substitutes the value of $HOME loaded by a login shell, so it is the value belonging to user yy. The third command does not rely on a $HOME value and creates a file in the guessed home directory of user yy.
Why does the fourth command fail? The intention is that it writes the same file, but relying on the $HOME variable belonging to user yy to ensure it actually does end up in her home directory. I don't understand why a login shell breaks the behaviour of a here-doc command passed in as a static single-quoted string. The failure of the fifth command verifies that this problem is not about variable substitution.
xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
xx@host ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
These commands were issued on a Linux Mint 18.3 Cinnamon 64-bit system, which is based on Ubuntu 16.04 (Xenial Xerus).
Update: The here-doc aspect is just clouding the issue. Here's a simplification of the problem:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
Why does the first of those two commands preserve the linebreak and the second does not? sudo
is common to both commands, yet seems to be escaping/filtering/interpolating differently depending on nothing but the "-i" option.
bash sudo here-document
|
show 6 more comments
In the sequence of five commands below, all depend on single-quotes to hand off possible variable substitution to the called bash
shell rather than the calling shell. The calling user is xx, but the called shell will be run as user yy. The first command substitutes $HOME with the calling shell's value because the called shell is not a login shell. The second command substitutes the value of $HOME loaded by a login shell, so it is the value belonging to user yy. The third command does not rely on a $HOME value and creates a file in the guessed home directory of user yy.
Why does the fourth command fail? The intention is that it writes the same file, but relying on the $HOME variable belonging to user yy to ensure it actually does end up in her home directory. I don't understand why a login shell breaks the behaviour of a here-doc command passed in as a static single-quoted string. The failure of the fifth command verifies that this problem is not about variable substitution.
xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
xx@host ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
These commands were issued on a Linux Mint 18.3 Cinnamon 64-bit system, which is based on Ubuntu 16.04 (Xenial Xerus).
Update: The here-doc aspect is just clouding the issue. Here's a simplification of the problem:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
Why does the first of those two commands preserve the linebreak and the second does not? sudo
is common to both commands, yet seems to be escaping/filtering/interpolating differently depending on nothing but the "-i" option.
bash sudo here-document
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with:%s/^/ /
where there are four spaces between the final two slashes.)
– Wildcard
Dec 13 '17 at 5:02
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
By the way, to get the$HOME
value for another user, just use tilde expansion. You don't needsudo -i
at all. SeeLESS='+/^ *Tilde Expansion' man bash
and also see my answer below.
– Wildcard
Dec 13 '17 at 5:22
|
show 6 more comments
In the sequence of five commands below, all depend on single-quotes to hand off possible variable substitution to the called bash
shell rather than the calling shell. The calling user is xx, but the called shell will be run as user yy. The first command substitutes $HOME with the calling shell's value because the called shell is not a login shell. The second command substitutes the value of $HOME loaded by a login shell, so it is the value belonging to user yy. The third command does not rely on a $HOME value and creates a file in the guessed home directory of user yy.
Why does the fourth command fail? The intention is that it writes the same file, but relying on the $HOME variable belonging to user yy to ensure it actually does end up in her home directory. I don't understand why a login shell breaks the behaviour of a here-doc command passed in as a static single-quoted string. The failure of the fifth command verifies that this problem is not about variable substitution.
xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
xx@host ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
These commands were issued on a Linux Mint 18.3 Cinnamon 64-bit system, which is based on Ubuntu 16.04 (Xenial Xerus).
Update: The here-doc aspect is just clouding the issue. Here's a simplification of the problem:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
Why does the first of those two commands preserve the linebreak and the second does not? sudo
is common to both commands, yet seems to be escaping/filtering/interpolating differently depending on nothing but the "-i" option.
bash sudo here-document
In the sequence of five commands below, all depend on single-quotes to hand off possible variable substitution to the called bash
shell rather than the calling shell. The calling user is xx, but the called shell will be run as user yy. The first command substitutes $HOME with the calling shell's value because the called shell is not a login shell. The second command substitutes the value of $HOME loaded by a login shell, so it is the value belonging to user yy. The third command does not rely on a $HOME value and creates a file in the guessed home directory of user yy.
Why does the fourth command fail? The intention is that it writes the same file, but relying on the $HOME variable belonging to user yy to ensure it actually does end up in her home directory. I don't understand why a login shell breaks the behaviour of a here-doc command passed in as a static single-quoted string. The failure of the fifth command verifies that this problem is not about variable substitution.
xx@host ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
xx@host ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
xx@host ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
xx@host ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
xx@host ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
These commands were issued on a Linux Mint 18.3 Cinnamon 64-bit system, which is based on Ubuntu 16.04 (Xenial Xerus).
Update: The here-doc aspect is just clouding the issue. Here's a simplification of the problem:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
Why does the first of those two commands preserve the linebreak and the second does not? sudo
is common to both commands, yet seems to be escaping/filtering/interpolating differently depending on nothing but the "-i" option.
bash sudo here-document
bash sudo here-document
edited Dec 15 '17 at 4:47
![](https://i.stack.imgur.com/O11k5.jpg?s=32&g=1)
![](https://i.stack.imgur.com/O11k5.jpg?s=32&g=1)
ilkkachu
60.3k1098171
60.3k1098171
asked Dec 13 '17 at 1:00
froagefroage
123213
123213
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with:%s/^/ /
where there are four spaces between the final two slashes.)
– Wildcard
Dec 13 '17 at 5:02
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
By the way, to get the$HOME
value for another user, just use tilde expansion. You don't needsudo -i
at all. SeeLESS='+/^ *Tilde Expansion' man bash
and also see my answer below.
– Wildcard
Dec 13 '17 at 5:22
|
show 6 more comments
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with:%s/^/ /
where there are four spaces between the final two slashes.)
– Wildcard
Dec 13 '17 at 5:02
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
By the way, to get the$HOME
value for another user, just use tilde expansion. You don't needsudo -i
at all. SeeLESS='+/^ *Tilde Expansion' man bash
and also see my answer below.
– Wildcard
Dec 13 '17 at 5:22
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with
:%s/^/ /
where there are four spaces between the final two slashes.)– Wildcard
Dec 13 '17 at 5:02
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with
:%s/^/ /
where there are four spaces between the final two slashes.)– Wildcard
Dec 13 '17 at 5:02
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
By the way, to get the
$HOME
value for another user, just use tilde expansion. You don't need sudo -i
at all. See LESS='+/^ *Tilde Expansion' man bash
and also see my answer below.– Wildcard
Dec 13 '17 at 5:22
By the way, to get the
$HOME
value for another user, just use tilde expansion. You don't need sudo -i
at all. See LESS='+/^ *Tilde Expansion' man bash
and also see my answer below.– Wildcard
Dec 13 '17 at 5:22
|
show 6 more comments
2 Answers
2
active
oldest
votes
The documentation for -i
states:
The
-i
(simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell. This means that login-specific resource files such as .profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.
That is, it genuinely runs the user's login shell, and then passes whatever command you gave sudo
to it using -c
- unlike what sudo cmd arg arg
usually does without the -i
option. Ordinarily, sudo
just uses one of the exec*
functions directly to start the process itself, with no intermediate shell and all arguments passed through exactly as-is.
With -i
, it sets up the environment, runs the user's shell as a login shell, and reconstructs the command you asked to run as an argument to bash -c
. In your case, it runs (say, approximately) /bin/bash -c "bash -c ' ... '"
(imagine quoting that works).
The problem lies in how sudo
turns the command you wrote into something that -c
can deal with, explained in the next section. The last section has some possible solutions, and in between is some debugging and verification technique,
Why does this happen?
When passing the command to -c
, it needs some preprocessing to make it do the right thing, which sudo
does in advance of running the shell. For example, if your command is:
sudo -iu yy echo 'three spaces'
then those spaces need to be escaped in order for the command to mean the same thing (i.e., for the single argument not to be split into two words). What ends up being run is:
/bin/bash -c 'echo three spaces'
Let's start with your simplified command:
sudo bash -c 'echo 1
echo 2'
In this case, sudo
changes user, then runs execvp("bash", ["bash", "-c", "echo 1necho 2"])
(for an invented array literal syntax).
With -i
:
sudo -i bash -c 'echo 1
echo 2'
instead it changes user, then runs execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, where \
equates to a literal and
n
is a linebreak. It's escaped the spaces and the newline in your main command by preceding them with backslashes.
That is, there's an outer login shell, which has two arguments: -c
and your entire command, reconstructed into a form the shell is expected to understand correctly. Unfortunately, it doesn't. The inner bash
command ultimately tries to run:
echo 1
echo 2
where the first physical line ends with a line continuation (backslash followed by newline), which is deleted entirely. The logical line is then just echo 1echo 2
, which doesn't do what you wanted.
There's an argument that this is a flaw in sudo
's escaping, given the standard behaviour of backslash-newline pairs. I think it should be safe to leave them unescaped here.
The same happens for your command with a here-document. It runs as, roughly:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
where 12
represents an actual newline - sudo
has inserted a backslash before each of them, just like the spaces. Note the double-escaping on \012
: that's ps
's rendition of an actual backslash followed by newline, which I'm using here (see below). What eventually runs is:
bash -c 'cat << "EOF"
script-content
EOF
'
with line continuations + newline everywhere, which are just removed. That makes it one long line, with no actual newlines in it, and an invalid heredoc:
bash -c 'cat << "EOF"script-contentEOF'
So that's your problem: the inner bash
process gets only one line of command, and so the here-document never gets a chance to end (or start). I have some imperfect solutions at the bottom, but this is the underlying cause.
How can you check what's happening?
To get those commands out correctly quoted and validate what was happening I modified my login shell's profile file (.profile
, .bash_profile
, .zprofile
, etc) to say just:
ps awx|grep $$
That shows me the command line of the running shell at the time and gives me an extra couple of lines of output before the warning.
hexdump -C /proc/$$/cmdline
will also be helpful on Linux.
What can you do about it?
I don't see an obvious and reliable way of getting what you want out of this. You don't want sudo
to touch your command at all if possible. One option that will largely work for a simple case is to pipe the commands into the shell, rather than specifying them on the command line:
printf 'cat > ... << ... n ...' | sudo -iu yy
That requires careful internal escaping still.
Probably better is just to put them into a temporary script file and run them that way:
f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"
A made-up filename of your own will work too. Depending on your sudoers settings you might be able to keep a file descriptor open and have it read from that as a file (/dev/fd/3
) if you really don't want it on disk, but a real file is going to be easier.
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
I've edited to elaborate on where this is all happening: the issue is thatsudo
escapes everything it isn't sure about, and that includes newlines, but
– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
|
show 2 more comments
The more general need that brought this to light is to document sequences of commands that can be copy 'n pasted directly to a terminal session. Amongst these are commands that create small files in a manner that is human readable and still documents every step. Creating temporary files or directing the reader to "use an editor" deviates from the goal of what is essentially a 100% playback possibility. – froage 4 mins ago
If that's all you're trying to do, just use:
sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF
Much simpler and you don't need to fiddle about with the quoting problems of sticking the whole thing inside a bash -c '...'
construct. Especially if you have any quotes in your script, this will make it a lot easier.
(Note that the heredoc delimiter definition is quoted as in 'EOF'
so that any variables you use in the script-content won't be expanded during the creation of test.sh
, which would likely not be your intention.)
Also note the use of tilde expansion to get user yy
's home directory: ~yy
. See LESS='+/^ *Tilde Expansion' man bash
for more on this. That's why you don't need sudo -i
.
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to usetee
instead ofcat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.
– froage
Dec 13 '17 at 5:26
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug insudo -i
as well, someday.)
– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "106"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f410531%2fwhy-does-a-sudo-i-login-shell-break-a-here-doc-command-string-argument%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
The documentation for -i
states:
The
-i
(simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell. This means that login-specific resource files such as .profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.
That is, it genuinely runs the user's login shell, and then passes whatever command you gave sudo
to it using -c
- unlike what sudo cmd arg arg
usually does without the -i
option. Ordinarily, sudo
just uses one of the exec*
functions directly to start the process itself, with no intermediate shell and all arguments passed through exactly as-is.
With -i
, it sets up the environment, runs the user's shell as a login shell, and reconstructs the command you asked to run as an argument to bash -c
. In your case, it runs (say, approximately) /bin/bash -c "bash -c ' ... '"
(imagine quoting that works).
The problem lies in how sudo
turns the command you wrote into something that -c
can deal with, explained in the next section. The last section has some possible solutions, and in between is some debugging and verification technique,
Why does this happen?
When passing the command to -c
, it needs some preprocessing to make it do the right thing, which sudo
does in advance of running the shell. For example, if your command is:
sudo -iu yy echo 'three spaces'
then those spaces need to be escaped in order for the command to mean the same thing (i.e., for the single argument not to be split into two words). What ends up being run is:
/bin/bash -c 'echo three spaces'
Let's start with your simplified command:
sudo bash -c 'echo 1
echo 2'
In this case, sudo
changes user, then runs execvp("bash", ["bash", "-c", "echo 1necho 2"])
(for an invented array literal syntax).
With -i
:
sudo -i bash -c 'echo 1
echo 2'
instead it changes user, then runs execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, where \
equates to a literal and
n
is a linebreak. It's escaped the spaces and the newline in your main command by preceding them with backslashes.
That is, there's an outer login shell, which has two arguments: -c
and your entire command, reconstructed into a form the shell is expected to understand correctly. Unfortunately, it doesn't. The inner bash
command ultimately tries to run:
echo 1
echo 2
where the first physical line ends with a line continuation (backslash followed by newline), which is deleted entirely. The logical line is then just echo 1echo 2
, which doesn't do what you wanted.
There's an argument that this is a flaw in sudo
's escaping, given the standard behaviour of backslash-newline pairs. I think it should be safe to leave them unescaped here.
The same happens for your command with a here-document. It runs as, roughly:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
where 12
represents an actual newline - sudo
has inserted a backslash before each of them, just like the spaces. Note the double-escaping on \012
: that's ps
's rendition of an actual backslash followed by newline, which I'm using here (see below). What eventually runs is:
bash -c 'cat << "EOF"
script-content
EOF
'
with line continuations + newline everywhere, which are just removed. That makes it one long line, with no actual newlines in it, and an invalid heredoc:
bash -c 'cat << "EOF"script-contentEOF'
So that's your problem: the inner bash
process gets only one line of command, and so the here-document never gets a chance to end (or start). I have some imperfect solutions at the bottom, but this is the underlying cause.
How can you check what's happening?
To get those commands out correctly quoted and validate what was happening I modified my login shell's profile file (.profile
, .bash_profile
, .zprofile
, etc) to say just:
ps awx|grep $$
That shows me the command line of the running shell at the time and gives me an extra couple of lines of output before the warning.
hexdump -C /proc/$$/cmdline
will also be helpful on Linux.
What can you do about it?
I don't see an obvious and reliable way of getting what you want out of this. You don't want sudo
to touch your command at all if possible. One option that will largely work for a simple case is to pipe the commands into the shell, rather than specifying them on the command line:
printf 'cat > ... << ... n ...' | sudo -iu yy
That requires careful internal escaping still.
Probably better is just to put them into a temporary script file and run them that way:
f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"
A made-up filename of your own will work too. Depending on your sudoers settings you might be able to keep a file descriptor open and have it read from that as a file (/dev/fd/3
) if you really don't want it on disk, but a real file is going to be easier.
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
I've edited to elaborate on where this is all happening: the issue is thatsudo
escapes everything it isn't sure about, and that includes newlines, but
– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
|
show 2 more comments
The documentation for -i
states:
The
-i
(simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell. This means that login-specific resource files such as .profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.
That is, it genuinely runs the user's login shell, and then passes whatever command you gave sudo
to it using -c
- unlike what sudo cmd arg arg
usually does without the -i
option. Ordinarily, sudo
just uses one of the exec*
functions directly to start the process itself, with no intermediate shell and all arguments passed through exactly as-is.
With -i
, it sets up the environment, runs the user's shell as a login shell, and reconstructs the command you asked to run as an argument to bash -c
. In your case, it runs (say, approximately) /bin/bash -c "bash -c ' ... '"
(imagine quoting that works).
The problem lies in how sudo
turns the command you wrote into something that -c
can deal with, explained in the next section. The last section has some possible solutions, and in between is some debugging and verification technique,
Why does this happen?
When passing the command to -c
, it needs some preprocessing to make it do the right thing, which sudo
does in advance of running the shell. For example, if your command is:
sudo -iu yy echo 'three spaces'
then those spaces need to be escaped in order for the command to mean the same thing (i.e., for the single argument not to be split into two words). What ends up being run is:
/bin/bash -c 'echo three spaces'
Let's start with your simplified command:
sudo bash -c 'echo 1
echo 2'
In this case, sudo
changes user, then runs execvp("bash", ["bash", "-c", "echo 1necho 2"])
(for an invented array literal syntax).
With -i
:
sudo -i bash -c 'echo 1
echo 2'
instead it changes user, then runs execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, where \
equates to a literal and
n
is a linebreak. It's escaped the spaces and the newline in your main command by preceding them with backslashes.
That is, there's an outer login shell, which has two arguments: -c
and your entire command, reconstructed into a form the shell is expected to understand correctly. Unfortunately, it doesn't. The inner bash
command ultimately tries to run:
echo 1
echo 2
where the first physical line ends with a line continuation (backslash followed by newline), which is deleted entirely. The logical line is then just echo 1echo 2
, which doesn't do what you wanted.
There's an argument that this is a flaw in sudo
's escaping, given the standard behaviour of backslash-newline pairs. I think it should be safe to leave them unescaped here.
The same happens for your command with a here-document. It runs as, roughly:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
where 12
represents an actual newline - sudo
has inserted a backslash before each of them, just like the spaces. Note the double-escaping on \012
: that's ps
's rendition of an actual backslash followed by newline, which I'm using here (see below). What eventually runs is:
bash -c 'cat << "EOF"
script-content
EOF
'
with line continuations + newline everywhere, which are just removed. That makes it one long line, with no actual newlines in it, and an invalid heredoc:
bash -c 'cat << "EOF"script-contentEOF'
So that's your problem: the inner bash
process gets only one line of command, and so the here-document never gets a chance to end (or start). I have some imperfect solutions at the bottom, but this is the underlying cause.
How can you check what's happening?
To get those commands out correctly quoted and validate what was happening I modified my login shell's profile file (.profile
, .bash_profile
, .zprofile
, etc) to say just:
ps awx|grep $$
That shows me the command line of the running shell at the time and gives me an extra couple of lines of output before the warning.
hexdump -C /proc/$$/cmdline
will also be helpful on Linux.
What can you do about it?
I don't see an obvious and reliable way of getting what you want out of this. You don't want sudo
to touch your command at all if possible. One option that will largely work for a simple case is to pipe the commands into the shell, rather than specifying them on the command line:
printf 'cat > ... << ... n ...' | sudo -iu yy
That requires careful internal escaping still.
Probably better is just to put them into a temporary script file and run them that way:
f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"
A made-up filename of your own will work too. Depending on your sudoers settings you might be able to keep a file descriptor open and have it read from that as a file (/dev/fd/3
) if you really don't want it on disk, but a real file is going to be easier.
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
I've edited to elaborate on where this is all happening: the issue is thatsudo
escapes everything it isn't sure about, and that includes newlines, but
– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
|
show 2 more comments
The documentation for -i
states:
The
-i
(simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell. This means that login-specific resource files such as .profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.
That is, it genuinely runs the user's login shell, and then passes whatever command you gave sudo
to it using -c
- unlike what sudo cmd arg arg
usually does without the -i
option. Ordinarily, sudo
just uses one of the exec*
functions directly to start the process itself, with no intermediate shell and all arguments passed through exactly as-is.
With -i
, it sets up the environment, runs the user's shell as a login shell, and reconstructs the command you asked to run as an argument to bash -c
. In your case, it runs (say, approximately) /bin/bash -c "bash -c ' ... '"
(imagine quoting that works).
The problem lies in how sudo
turns the command you wrote into something that -c
can deal with, explained in the next section. The last section has some possible solutions, and in between is some debugging and verification technique,
Why does this happen?
When passing the command to -c
, it needs some preprocessing to make it do the right thing, which sudo
does in advance of running the shell. For example, if your command is:
sudo -iu yy echo 'three spaces'
then those spaces need to be escaped in order for the command to mean the same thing (i.e., for the single argument not to be split into two words). What ends up being run is:
/bin/bash -c 'echo three spaces'
Let's start with your simplified command:
sudo bash -c 'echo 1
echo 2'
In this case, sudo
changes user, then runs execvp("bash", ["bash", "-c", "echo 1necho 2"])
(for an invented array literal syntax).
With -i
:
sudo -i bash -c 'echo 1
echo 2'
instead it changes user, then runs execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, where \
equates to a literal and
n
is a linebreak. It's escaped the spaces and the newline in your main command by preceding them with backslashes.
That is, there's an outer login shell, which has two arguments: -c
and your entire command, reconstructed into a form the shell is expected to understand correctly. Unfortunately, it doesn't. The inner bash
command ultimately tries to run:
echo 1
echo 2
where the first physical line ends with a line continuation (backslash followed by newline), which is deleted entirely. The logical line is then just echo 1echo 2
, which doesn't do what you wanted.
There's an argument that this is a flaw in sudo
's escaping, given the standard behaviour of backslash-newline pairs. I think it should be safe to leave them unescaped here.
The same happens for your command with a here-document. It runs as, roughly:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
where 12
represents an actual newline - sudo
has inserted a backslash before each of them, just like the spaces. Note the double-escaping on \012
: that's ps
's rendition of an actual backslash followed by newline, which I'm using here (see below). What eventually runs is:
bash -c 'cat << "EOF"
script-content
EOF
'
with line continuations + newline everywhere, which are just removed. That makes it one long line, with no actual newlines in it, and an invalid heredoc:
bash -c 'cat << "EOF"script-contentEOF'
So that's your problem: the inner bash
process gets only one line of command, and so the here-document never gets a chance to end (or start). I have some imperfect solutions at the bottom, but this is the underlying cause.
How can you check what's happening?
To get those commands out correctly quoted and validate what was happening I modified my login shell's profile file (.profile
, .bash_profile
, .zprofile
, etc) to say just:
ps awx|grep $$
That shows me the command line of the running shell at the time and gives me an extra couple of lines of output before the warning.
hexdump -C /proc/$$/cmdline
will also be helpful on Linux.
What can you do about it?
I don't see an obvious and reliable way of getting what you want out of this. You don't want sudo
to touch your command at all if possible. One option that will largely work for a simple case is to pipe the commands into the shell, rather than specifying them on the command line:
printf 'cat > ... << ... n ...' | sudo -iu yy
That requires careful internal escaping still.
Probably better is just to put them into a temporary script file and run them that way:
f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"
A made-up filename of your own will work too. Depending on your sudoers settings you might be able to keep a file descriptor open and have it read from that as a file (/dev/fd/3
) if you really don't want it on disk, but a real file is going to be easier.
The documentation for -i
states:
The
-i
(simulate initial login) option runs the shell specified by the password database entry of the target user as a login shell. This means that login-specific resource files such as .profile or .login will be read by the shell. If a command is specified, it is passed to the shell for execution via the shell's -c option.
That is, it genuinely runs the user's login shell, and then passes whatever command you gave sudo
to it using -c
- unlike what sudo cmd arg arg
usually does without the -i
option. Ordinarily, sudo
just uses one of the exec*
functions directly to start the process itself, with no intermediate shell and all arguments passed through exactly as-is.
With -i
, it sets up the environment, runs the user's shell as a login shell, and reconstructs the command you asked to run as an argument to bash -c
. In your case, it runs (say, approximately) /bin/bash -c "bash -c ' ... '"
(imagine quoting that works).
The problem lies in how sudo
turns the command you wrote into something that -c
can deal with, explained in the next section. The last section has some possible solutions, and in between is some debugging and verification technique,
Why does this happen?
When passing the command to -c
, it needs some preprocessing to make it do the right thing, which sudo
does in advance of running the shell. For example, if your command is:
sudo -iu yy echo 'three spaces'
then those spaces need to be escaped in order for the command to mean the same thing (i.e., for the single argument not to be split into two words). What ends up being run is:
/bin/bash -c 'echo three spaces'
Let's start with your simplified command:
sudo bash -c 'echo 1
echo 2'
In this case, sudo
changes user, then runs execvp("bash", ["bash", "-c", "echo 1necho 2"])
(for an invented array literal syntax).
With -i
:
sudo -i bash -c 'echo 1
echo 2'
instead it changes user, then runs execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, where \
equates to a literal and
n
is a linebreak. It's escaped the spaces and the newline in your main command by preceding them with backslashes.
That is, there's an outer login shell, which has two arguments: -c
and your entire command, reconstructed into a form the shell is expected to understand correctly. Unfortunately, it doesn't. The inner bash
command ultimately tries to run:
echo 1
echo 2
where the first physical line ends with a line continuation (backslash followed by newline), which is deleted entirely. The logical line is then just echo 1echo 2
, which doesn't do what you wanted.
There's an argument that this is a flaw in sudo
's escaping, given the standard behaviour of backslash-newline pairs. I think it should be safe to leave them unescaped here.
The same happens for your command with a here-document. It runs as, roughly:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
where 12
represents an actual newline - sudo
has inserted a backslash before each of them, just like the spaces. Note the double-escaping on \012
: that's ps
's rendition of an actual backslash followed by newline, which I'm using here (see below). What eventually runs is:
bash -c 'cat << "EOF"
script-content
EOF
'
with line continuations + newline everywhere, which are just removed. That makes it one long line, with no actual newlines in it, and an invalid heredoc:
bash -c 'cat << "EOF"script-contentEOF'
So that's your problem: the inner bash
process gets only one line of command, and so the here-document never gets a chance to end (or start). I have some imperfect solutions at the bottom, but this is the underlying cause.
How can you check what's happening?
To get those commands out correctly quoted and validate what was happening I modified my login shell's profile file (.profile
, .bash_profile
, .zprofile
, etc) to say just:
ps awx|grep $$
That shows me the command line of the running shell at the time and gives me an extra couple of lines of output before the warning.
hexdump -C /proc/$$/cmdline
will also be helpful on Linux.
What can you do about it?
I don't see an obvious and reliable way of getting what you want out of this. You don't want sudo
to touch your command at all if possible. One option that will largely work for a simple case is to pipe the commands into the shell, rather than specifying them on the command line:
printf 'cat > ... << ... n ...' | sudo -iu yy
That requires careful internal escaping still.
Probably better is just to put them into a temporary script file and run them that way:
f=`mktemp`
printf 'command' > "$f"
chmod +r "$f"
sudo -iu yy "$f"
rm "$f"
A made-up filename of your own will work too. Depending on your sudoers settings you might be able to keep a file descriptor open and have it read from that as a file (/dev/fd/3
) if you really don't want it on disk, but a real file is going to be easier.
edited 50 mins ago
answered Dec 13 '17 at 3:07
![](https://i.stack.imgur.com/AjIZY.jpg?s=32&g=1)
![](https://i.stack.imgur.com/AjIZY.jpg?s=32&g=1)
Michael HomerMichael Homer
49.4k8133172
49.4k8133172
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
I've edited to elaborate on where this is all happening: the issue is thatsudo
escapes everything it isn't sure about, and that includes newlines, but
– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
|
show 2 more comments
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
I've edited to elaborate on where this is all happening: the issue is thatsudo
escapes everything it isn't sure about, and that includes newlines, but
– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
That's a good explanation of how a multi-line command is collapsed into a single command line before execution and I knew that was happening, but I don't see why that is any sort of problem. As you have identified though, the \012 seems to be stripped out. Why? That seems to me to be the real problem. Why does switching from a non-login shell to a login shell cause any characters to be stripped out?
– froage
Dec 13 '17 at 3:48
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
Because a heredoc starts on one line and ends on a subsequent line; you only have the one line.
– Michael Homer
Dec 13 '17 at 3:49
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
The login shell difference is because it’s not “non-login shell”, it’s (login) shell or no shell at all.
– Michael Homer
Dec 13 '17 at 3:50
1
1
I've edited to elaborate on where this is all happening: the issue is that
sudo
escapes everything it isn't sure about, and that includes newlines, but
+ newline actually means something different. Arguably it's a bug in sudo. I don't see a way around it within your command structure.– Michael Homer
Dec 13 '17 at 4:39
I've edited to elaborate on where this is all happening: the issue is that
sudo
escapes everything it isn't sure about, and that includes newlines, but
+ newline actually means something different. Arguably it's a bug in sudo. I don't see a way around it within your command structure.– Michael Homer
Dec 13 '17 at 4:39
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
I've updated my question with a simpler distillation of the problem. It certainly does feel like a bug.
– froage
Dec 13 '17 at 4:57
|
show 2 more comments
The more general need that brought this to light is to document sequences of commands that can be copy 'n pasted directly to a terminal session. Amongst these are commands that create small files in a manner that is human readable and still documents every step. Creating temporary files or directing the reader to "use an editor" deviates from the goal of what is essentially a 100% playback possibility. – froage 4 mins ago
If that's all you're trying to do, just use:
sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF
Much simpler and you don't need to fiddle about with the quoting problems of sticking the whole thing inside a bash -c '...'
construct. Especially if you have any quotes in your script, this will make it a lot easier.
(Note that the heredoc delimiter definition is quoted as in 'EOF'
so that any variables you use in the script-content won't be expanded during the creation of test.sh
, which would likely not be your intention.)
Also note the use of tilde expansion to get user yy
's home directory: ~yy
. See LESS='+/^ *Tilde Expansion' man bash
for more on this. That's why you don't need sudo -i
.
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to usetee
instead ofcat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.
– froage
Dec 13 '17 at 5:26
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug insudo -i
as well, someday.)
– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
add a comment |
The more general need that brought this to light is to document sequences of commands that can be copy 'n pasted directly to a terminal session. Amongst these are commands that create small files in a manner that is human readable and still documents every step. Creating temporary files or directing the reader to "use an editor" deviates from the goal of what is essentially a 100% playback possibility. – froage 4 mins ago
If that's all you're trying to do, just use:
sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF
Much simpler and you don't need to fiddle about with the quoting problems of sticking the whole thing inside a bash -c '...'
construct. Especially if you have any quotes in your script, this will make it a lot easier.
(Note that the heredoc delimiter definition is quoted as in 'EOF'
so that any variables you use in the script-content won't be expanded during the creation of test.sh
, which would likely not be your intention.)
Also note the use of tilde expansion to get user yy
's home directory: ~yy
. See LESS='+/^ *Tilde Expansion' man bash
for more on this. That's why you don't need sudo -i
.
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to usetee
instead ofcat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.
– froage
Dec 13 '17 at 5:26
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug insudo -i
as well, someday.)
– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
add a comment |
The more general need that brought this to light is to document sequences of commands that can be copy 'n pasted directly to a terminal session. Amongst these are commands that create small files in a manner that is human readable and still documents every step. Creating temporary files or directing the reader to "use an editor" deviates from the goal of what is essentially a 100% playback possibility. – froage 4 mins ago
If that's all you're trying to do, just use:
sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF
Much simpler and you don't need to fiddle about with the quoting problems of sticking the whole thing inside a bash -c '...'
construct. Especially if you have any quotes in your script, this will make it a lot easier.
(Note that the heredoc delimiter definition is quoted as in 'EOF'
so that any variables you use in the script-content won't be expanded during the creation of test.sh
, which would likely not be your intention.)
Also note the use of tilde expansion to get user yy
's home directory: ~yy
. See LESS='+/^ *Tilde Expansion' man bash
for more on this. That's why you don't need sudo -i
.
The more general need that brought this to light is to document sequences of commands that can be copy 'n pasted directly to a terminal session. Amongst these are commands that create small files in a manner that is human readable and still documents every step. Creating temporary files or directing the reader to "use an editor" deviates from the goal of what is essentially a 100% playback possibility. – froage 4 mins ago
If that's all you're trying to do, just use:
sudo -u yy tee ~yy/test.sh >/dev/null <<'EOF'
script-content
EOF
Much simpler and you don't need to fiddle about with the quoting problems of sticking the whole thing inside a bash -c '...'
construct. Especially if you have any quotes in your script, this will make it a lot easier.
(Note that the heredoc delimiter definition is quoted as in 'EOF'
so that any variables you use in the script-content won't be expanded during the creation of test.sh
, which would likely not be your intention.)
Also note the use of tilde expansion to get user yy
's home directory: ~yy
. See LESS='+/^ *Tilde Expansion' man bash
for more on this. That's why you don't need sudo -i
.
edited Dec 13 '17 at 5:23
answered Dec 13 '17 at 5:16
![](https://i.stack.imgur.com/SbCyV.png?s=32&g=1)
![](https://i.stack.imgur.com/SbCyV.png?s=32&g=1)
WildcardWildcard
23k1065169
23k1065169
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to usetee
instead ofcat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.
– froage
Dec 13 '17 at 5:26
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug insudo -i
as well, someday.)
– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
add a comment |
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to usetee
instead ofcat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.
– froage
Dec 13 '17 at 5:26
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug insudo -i
as well, someday.)
– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
2
2
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to use
tee
instead of cat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.– froage
Dec 13 '17 at 5:26
You are quoting an informational side comment to Michael in response to his workaround, not answering the question I submitted to Stack Exchange. While I really appreciate the suggestion to use
tee
instead of cat
and will definitely adopt it, I trust you understand why I can't accept this as an answer.– froage
Dec 13 '17 at 5:26
1
1
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug in
sudo -i
as well, someday.)– Wildcard
Dec 13 '17 at 6:37
You're absolutely right; this isn't an answer to the actual posted question, it just addresses the use case underlying your particular situation. I'm glad it will still help you, though! That's why I wrote it. :) And I agree it wouldn't be appropriate to accept this answer. (I considered just posting it as a comment but it would have been much too long. Maybe it will help someone else work around the bug in
sudo -i
as well, someday.)– Wildcard
Dec 13 '17 at 6:37
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
It just goes to show that deviating from rigid rules/conventions to solve a problem sometimes serves a good purpose ;-)
– froage
Dec 13 '17 at 8:36
add a comment |
Thanks for contributing an answer to Unix & Linux Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f410531%2fwhy-does-a-sudo-i-login-shell-break-a-here-doc-command-string-argument%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
@Tigger: That's not how here-docs work. Quoting the limit string (first EOF) means that parameter substitution is suppressed for the content of the here-doc. The quotes are not part of the limit string. See tldp.org/LDP/abs/html/here-docs.html.
– froage
Dec 13 '17 at 1:17
Another thing clouding this issue is that you're using peculiar formatting. Please use code blocks, not block quotes. Just take the original text copy-pasted from your terminal, and insert four spaces at the beginning of every line. (You can do it in a gVim buffer with
:%s/^/ /
where there are four spaces between the final two slashes.)– Wildcard
Dec 13 '17 at 5:02
@Wildcard: I would normally do as you suggest. I recognize it was peculiar formatting, but it was a (possibly misguided) attempt to make it clear what was actually being typed in light of the fact that novice *nix users will not necessarily recognize the '> ' as a secondary prompt. Code blocks don't provide such flexibility, AFAIK.
– froage
Dec 13 '17 at 5:12
Usual practice on this site is to use one of two types of copy-paste: either just the commands you type without including any prompt (primary or secondary) or the output either, or include all prompts, output, etc. But either way put it in code blocks, not a block quote.
– Wildcard
Dec 13 '17 at 5:20
By the way, to get the
$HOME
value for another user, just use tilde expansion. You don't needsudo -i
at all. SeeLESS='+/^ *Tilde Expansion' man bash
and also see my answer below.– Wildcard
Dec 13 '17 at 5:22