How to use Windows environment variables in Vim script? - windows

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'.

Related

How to print regex pattern as string in terminal?

I'm trying to write a regex string in the terminal but zsh is interpreting this regex instead of just printing it. My shell code:
echo "((https?:\/\/(?:www\.|(?!www)))?[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})"
Current output:
zsh: event not found: www)))?[a
I already tried to use simple quotes, double quotes and no quotes.
If you type this command in a file and run as a script, it should be fine, unless you have explicitly enabled history expansion in your script. But then, you know what you are doing.
If you really literally hack such a huge command manually into an interactive shell, either turn off history expansion (by setopt nobanghist), or prefix your ! by a \ (unless the ! is already between single-quotes).
Example: Typing echo !www won't work, but typing echo \!www will.
If you never use history expansion, turning it off permanently would probably be the best choice.

Bash prompt changes when using arrow keys sometimes

When I use my terminal (iTerm 2 Mac) with my PS1 set to "\[\e[38;5;117m\W \e[39;38;5;104m\$\e[39;0m\] " and I use the arrow keys to go through my bash history it sometimes changes my prompt from ~ $ to just the first character of it and whatever command I'm looking at. For example, going to rvim .bashrc from randomDir $ ls. This problem also persists in the default terminal app.
\W and \$ should not go inside the \[...\], since bash will know how much space each takes up on the terminal.
PS1="\[\e[38;5;117m\]\W \[\e[39;38;5;104m\]\$\[\e[39;0m\] "
Only the characters that make up the ANSI escape sequence (which only instruct the terminal to change colors, without displaying a single additional character) are enclosed in \[...\].
Putting them inside \[...\] tells bash to ignore their contribution to the length of the prompt, leading to incorrect redraws.

bash shell access to $ProgramFiles(x86) environment variable

In the bash shell, I'm using git-bash.exe, how do I access the Windows 10 ProgramFiles(x86) environment variable?
If I execute printenv I see it in the output with the casing noted but attempts to access it using echo $ProgramFiles(x86), echo $ProgramFiles\(x86\) and echo $"ProgramFiles(x86)" do not work.
I am able to access the non-x86 version of that environment variable without any issue using echo $PROGRAMFILES and do relevant colon removal and backslash to forward replacements necessary to use it in PATH environment variable updates, e.g. PATH=$PATH:"/${PROGRAMFILES//:\\//} (x86)/Some App Path With Spaces/" followed by echo $PATH and printenv PATH that confirms the desired result. The issue is that I'd rather not have to compose the ProgramFiles(x86) environment variable versus being able to use it directly in updates to the PATH environment variable.
Along these same lines when trying to use the Windows APPDATA [ = C:\Users<username>\AppData\Roaming ] environment variable in updates to PATH environment variable I need to be able to replace not only the initial colon & backslash but also the subsequent backslashes with forward slashes. Using echo ${APPDATA//:\\//} produces C/Users\<username>\AppData\Roaming and I'm not aware of how to get the bash environment variable character matching and substitution syntax to cover both cases in order to produce the required C/Users/<username>/AppData/Roaming necessary for use in updates to PATH environment variable.
Note: there's a flaw in the process described below. In particular, if some environment variable is set to a multi-line value where one of the value lines matches the sed expression, you'll capture that line as well. To avoid this, if you have a Python available, you could use:
python -c 'import os; print(os.getenv("FOO(BAR)"))'
for instance. This will print None if the variable is not set, so you might want to make it fancier: e.g., supply a default value, or use sys.exit(1) if the variable is not set, for instance. (But if you have a Python interpreter available, you might consider writing in Python rather than directly in bash.)
Unix shell (sh, bash, etc) variable names—including environment variables—are constrained to character sets that exclude parentheses. In particular, "$FOO(BAR)" always parses as a reference to variable $FOO, followed by (BAR) as a separate word. This holds even with braceed expansion forms, where the separate word (BAR) is syntactically invalid:
bash$ echo "${FOO(BAR)}"
bash: ${FOO(BAR)}: bad substitution
Nonetheless, it is possible to set such variables, and access them, using other programs. For instance, using Python I set FOO(BAR) to hello:
>>> import os
>>> os.environ["FOO(BAR)"] = "hello"
>>> import subprocess
>>> subprocess.call("bash")
bash$
This bash instance cannot directly access the variable, but env prints all the variables:
bash$ env | grep FOO
FOO(BAR)=hello
If you have env (you probably do) and sed, you can combine them to extract arbitrary variables:
bash$ setting="$(env | sed -n 's/^FOO(BAR)=//p')"
bash$ echo "$setting"
hello
So assuming that Windows Bash doesn't have any special case to work around this particular clumsiness better, this same trick should work for "ProgramFiles(x86)".
Substituting multiple backslashes with forward slashes
You're mostly there: the problem is that your pattern looks specifically for :\ but the strings have multiple \s without colons. Your best bet is probably to have a program or function that actually understands Windows paths, as they don't necessarily have drive letters at the front (see https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats). But this pattern works for all-backslash:
bash$ v='a\b\c'
bash$ echo ${v//\\/\/}
a/b/c
The double slash means "substitute all occurrences". The pattern is then \\, which matches one backslash. The next slash introduces the replacement string, which is \/, which means one forward slash. (This can also be written as just / but I find that harder to read, oddly enough.)
Of course this does not replace the colon in C:, so we need one more substitution. You can't do that in one ${...} expansion, so the trick is to add another one:
bash$ v='C:\a\b\c'
bash$ echo ${v//\\/\/}
C:/a/b/c
bash$ v1="${v//\\//}"; echo ${v1/:/}
C/a/b/c
Put this inside a shell function, which you can eventually make smart enough to handle all valid paths, and that way you can use local to keep the variable name v1 from leaking.
Regarding APPDATA: The cygpath program can convert pathnames between Windows, Unix and "Mixed" conventions. Both Cygwin and Git for Windows come with this tool. Example:
$ echo "$APPDATA"
C:\Users\me\AppDataRoaming\
$ cygpath -u "$APPDATA"
/c/Users/me/AppData/Roaming
$ cygpath -m "$APPDATA"
C:/Users/me/AppData/Roaming
$ cygpath -w "$APPDATA"
C:\Users\me\AppData\Roaming
The "mixed" format is quite usefull because even most windows programs and Git for Windows can handle that format directly.
Assigning the output of cygpath to a variable works like this (note the quotes!):
$ XAPP=$(cygpath "$APPDATA")
$ echo "$XAPP"
$ cd "$XAPP"

What would cause bash to interpret ~ as a directory literally named "~"?

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.

command substitution but without breaking output into multiple arguments

Is there a way to do command substitution in BASH shell without breaking output into multiple arguments?
I copy the path of some directory (from the location bar in a GUI file browser) to clipboard and then issue the following command, where the command xsel returns the clipboard content, which is the path of the directory in this case:
cd `xsel`
But some path contain spaces or may even contain some special characters used by BASH.
How can I pass the output of a command as a single argument and without BASH messing with special characters?
cd "$(xsel)"
seems to handle all special characters (including $ and spaces).
My test string was boo*;cd.*($\: $_
$ mkdir "$(xsel)"
$ ls
boo*;cd.*($\: $_
$ file boo\*\;cd.\*\(\$\\\:\ \$_/
boo*;cd.*($\: $_/: directory
$ cd "$(xsel)"
$ pwd
/tmp/boo*;cd.*($\: $_
Have you tried:
cd "`xsel`"
That should do the job, unless you have dollars($) or back-slashes (\) in your path.
If you aren't doing this programmatically, most terminals in Linux let you paste from the clipboard with a middle-click on your mouse. Of course, you'll still need to put quotes before and after your paste, like #dave suggests.

Resources