Background
I'm using the Digital Ocean API to create a new server (droplet) and including a bash script to be automatically run when the server starts up the first time.
Problem
In my bash script (which runs as root), I try to install Oh-My-Zsh via its shell script, which tries to install it in ~. However, when I ssh into the machine, I find that Oh-My-Zsh has been installed into /~/ instead of /root.
Question
What could be causing bash to interpret ~ as the name of a folder instead of as an alias for $HOME, and is there anything I can do to fix this? The Oh My Zsh install script is not owned by me, so I can't simply change each ~ to $HOME or /root (of course I could hack together a sed command to do the replacement for me in that install script, but it seems like there should be an easier way...)
This can happen several ways. To take a few:
Why is a tilde in a path not expanded in a shell script? - If a tilde is literal content in a variable, tilde expansion will not take at variable expansion time.
How to manually expand a special variable (ex: ~ tilde) in bash - If a tilde expression is created via other expansions (ie. ~$user), alternate means are required to expand it.
If $HOME is not defined, tilde expansion will not take place.
Related
Out of curiosity I called which myscript.sh for a script I have under ~/bin/myscript.sh (in macOS's bash). I can execute the script with no problems from any directory without giving its full path. ~/bin is on my PATH and the script has executable flags set, ls -l outputs -rwxr-xr-x.
I would have expected which to show me the script's full path, but it didn't output anything.
Is this intended behaviour or is something odd happening here?
This happens when you add a literal tilde ~ to your PATH, instead of the actual path to your home directory. Rewriting ~ into /home/youruser is the job of the shell, not of the tool or filesystem. If you e.g. quote the "~", this rewrite doesn't happen and other tools like which get confused.
Here's information on this issue from the shellcheck wiki:
Literal tilde in PATH works poorly across programs.
Problematic code:
PATH="$PATH:~/bin"
Correct code:
PATH="$PATH:$HOME/bin"
Rationale:
Having literal ~ in PATH is a bad idea. Bash handles it, but nothing else does.
This means that even if you're always using Bash, you should avoid it because any invoked program that relies on PATH will effectively ignore those entries.
For example, make may say foo: Command not found even though foo works fine from the shell and Make and Bash both use the same PATH. You'll get similar messages from any non-bash scripts invoked, and whereis will come up empty.
Use $HOME or full path instead.
I tried to get the Android Studio launcher (studio.sh) to use my manually installed Java (not the system-wide default Java). Since I already declared PATH and JAVA_HOME in my .bashrc file, I simply sourced that file in the shell script:
. /home/foobar/.bashrc
but for some reason, $JAVA_HOME/bin/java was still not recognized as an executable file by the script.
I added some logging and found out that JAVA_HOME was expanded as ~/install/java..., i.e. the tilde operator was not expanded into the home directory.
I did some searching, but couldn't find any reason why it was not expanded. Is tilde a Bash-specific feature (the script uses #!/bin/sh, and Linux Mint uses dash, not bash)? Does tilde not work in some circumstances?
I replaced ~ with $HOME in my .bashrc declaration, and then it worked, so HOME is known at runtime.
In the bash manual, note that brace expansion during parameter substitution, but not recursively:
The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.
This implies that any tilde (or parameter references or command substitution) stored unexpanded in a bash variable will not automatically resolve. Your JAVA_HOME variable contains a literal tilde, so bash will not expand it automatically.
It is likely that your fix worked because tilde expansion does not apply in quotes:
$ echo "~"
~
$ echo ~
/home/jeffbowman
...but parameter expansion like $HOME does occur in quotes. Replacing it with $HOME expands to your home directory during the assignment of JAVA_HOME.
FOO=~/bar # stores /home/jeffbowman/bar
FOO="~/bar" # stores ~/bar
FOO=$HOME/bar # stores /home/jeffbowman/bar
FOO="$HOME/bar" # stores /home/jeffbowman/bar
Though the better option is to ensure your assignment is correct, if you want to expand it manually, these SO questions have some good options:
"Tilde expansion in quotes"
"How to manually expand a special variable (ex: ~ tilde) in bash"
I've noticed that I have a tendency to mistype ls as ;s, so I decided that I should just create an alias so that instead of throwing an error, it just runs the command I mean.
However, knowing that the semi-colon character has a meaning in shell scripts/commands, is there any way to allow me to create an alias with that the semi-colon key? I've tried the following to no avail:
alias ;s=ls
alias ";s"=ls
alias \;=ls
Is it possible to use the semi-colon as a character in a shell alias? And how do I do so in ZSH?
First and foremost: Consider solving your problem differently - fighting the shell grammar this way is asking for trouble.
As far as I can tell, while you can define such a command - albeit not with an alias - the only way to call it is quoted, e.g. as \;s - which defeats the purpose; read on for technical details.
An alias won't work: while zsh allows you to define it (which, arguably, it shouldn't), the very mechanism that would be required to call it - quoting - is also the very mechanism that bypasses aliases and thus prevents invocation.
You can, however, define a function (zsh only) or a script in your $PATH (works in zsh as well as in bash, ksh, and dash), as long as you invoke it quoted (e.g., as \;s or ';s' or ";s"), which defeats the purpose.
For the record, here are the command definitions, but, again, they can only be invoked quoted.
Function (works in zsh only; place in an initialization file such as ~/.zshrc):
';s'() { ls "$#" }
Executable script ;s (works in dash, bash, ksh and zsh; place in a directory in your $PATH):
#!/bin/sh
ls "$#"
There are several ways to run multiple commands at one go. One way is by separating each command with semicolon:
ls;who;banner Hi
Another way is by enclosing multiple commands in parenthesis.
(cd mydir;pwd)
What will happen by enclosing them in parenthesis?
The parentheses create a subshell. A subshell is a copy of them current shell, which means that state changes such as changing the working directory with cd or setting shell variables or exporting environment variables don't affect the original shell.
In the case here, the cd command changes the working directory, and pwd shows this. When the prompt returns you will still be in the same directory where you were before because cd changed the directory only in the subshell.
I have a Vim script that calls an external shell script and reads the output into the current buffer. It works fine in Unix systems. I'm trying to make the script OS-agnostic, so I want it to work for Windows users too. Here's the relevant line from the script:
:exe ":0r !$HOME/bin/shell_script"
According to Vim's docs, $HOME should translate fine for Windows users too. Using gvim on Win XP in command mode, doing
:echo $HOME
does indeed produce "C:\Documents and Settings\my_user".
However, my Vim script (adjusted for backslashes) fails on the Windows machine with a message in the DOS cmd.exe window saying
$HOME\bin\shell_script" not found.
In other words, Vim appears not to be expanding the value of $HOME before passing it to cmd.exe.
I can't use %APPDATA% either because Vim interprets % as the current file and pre/appends the file name to APPDATA. Weird that Vim does expand % but doesn't expand $HOME.
How do I get $HOME expanded correctly? Is it because I'm using exe in the vim script?
You don't need ! to read a file.
:exe ":0r $HOME/bin/shell_script"
Or read type command in windows(like cat in linux):
:exe '0r !type "'. $HOME . '\bin\shell_script"'
Note:
the type is executed in windows shell, so you need \(backslash) in path
if $HOME contains spaces, you need "(double-quote) to preserves the literal value of spaces
To clarify the answer given by kev:
On windows the $HOME variable do not expand properly when you escape to the console. For example, consider this code:
:e $HOME/myscript
This works because vim expands $HOME as normal. On the other hand this won't work:
:%! $HOME/myscript
Why? Because vim passes everything after ! to the underlying shell, which on Windows is cmd.exe which does environment variables %LIKE_THIS%. If you try to use that notation, vim will jump in and expand % to the name of the current file - go figure.
How to get around it? Use exe keyword:
:exe "%! ".$HOME."\myscript"
Let's analyze:
:exe is a command that takes a string and evaluates it (think eval in most languages)
"!% " the escape to shell command. Note that it is quoted so that exe can evaluate it. Also note how there is an extra space there so that when we append the home var it does not but right against it
.$HOME the dot is a string concatenation symbol. The $HOME is outside of the quotes but concatenated so that vim can expand it properly
."/myscript" path to script and possible arguments (also quoted)
The important thing here is keeping $HOME outside of the quote marks, otherwise it may not be properly expanded on Windows.
You probably need something like the expand function. For example:
:echo expand("$HOME/hello")
/home/amir/hello
You can find out more info about expand() with :help expand.
Here you have some information about slashes and backslashes in vim:
http://vimdoc.sourceforge.net/htmldoc/os_dos.html
When you prefer to use forward slashes, set the 'shellslash' option.
Vim will then replace backslashes with forward slashes when expanding
file names. This is especially useful when using a Unix-like 'shell'.