autocomplete bash vs. zsh - bash

I do have a simple function (in "reality" it is "a bit" more complex - just for illustration)
cat test.sh
_foocomplete(){
local list="abc\ndef\nghi"
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(echo -e ${list} | grep "${cur}") )
}
foo(){
echo "$1"
}
complete -F _foocomplete foo
Using this in bash, gives me
~ ❯ ps -o cmd= $$
bash
~ ❯ type foo
bash: type: foo: not found
~ ❯ source test.sh
~ ❯ foo <TAB>
abc def ghi
~ ❯ foo a<TAB>
abc
~ ❯ foo e<TAB>
def
Using it in zsh, is very similar
~ ❯ ps -o cmd= $$
-zsh
~ ❯ type -f foo
foo not found
~ ❯ source test.sh
~ ❯ foo <TAB>
abc def ghi
~ ❯ foo a<TAB>
abc
BUT that
~ ❯ foo e<TAB>
does not work (as in: does not, like in bash, give me def as option).
Is there - besides rewriting and using compdef - an "easy" explanation (and "fix")?
This is with no .zshrc (but also with e.g. Oh My Zsh)
As reply to autocomplete bash vs. zsh
cat test2.sh
foo(){
local list="abc\ndef\nghi"
echo -e ${list} | grep e
}
foo
~ ❯ /bin/zsh test2.sh
def
~ ❯ /bin/bash test2.sh
def
Thank you in advance

Zsh completion does "matching" itself while bash expects you to filter completion candidates with, e.g. grep. "def" does not begin with "e" so it is not completed by default. You can think of the default matching as putting * at just the cursor position.
I assume you must be using bashcompinit for that example to have even come close to working in zsh. Unfortunately that means that the usual hooks for configuring zsh completions are not available. bashcompinit leaves zsh's default matching active which as you observe makes it not behave in exactly the same way as bash - by using compadd's -U option it could have done that. Zsh's matching can usually be controlled with the matcher and matcher=list styles. Passing -M to compadd also works. The following is a basic native zsh completion similar to your bash example:
_foo() { compadd -M 'l:|=*' abc def ghi }
compdef _foo foo

Related

Alias a cmd in bash, without editing the script

Consider the following script
#!/bin/bash
abc
Here I want the "abc" cmd to be aliased to echo "Hi"(say)
But I don't have control to modify the script.
Is there a way to make the cmd behavior change without touching the script?
$ cat ./someScript.sh
#!/bin/bash
abc
$ ./someScript.sh
./someScript.sh: line 2: abc: command not found
Define a function named abc, and export it.
$ abc() { echo Hi; }
$ export -f abc
Now:
$ ./someScript.sh
Hi

Shell sourced file output piped vs redirected has different effects.

I'm struggling to understand the difference between | and > operators
I've looked in places like:
https://www.gnu.org/software/bash/manual/html_node/Redirections.html
and
Pipe vs redirect into process
But can't make enough sense of the explanations.
Here is my practical example:
test-a.sh:
alias testa='echo "alias testa here"'
echo "testa echo"
echo "testa echo2"
test-b.sh:
alias testb='echo "alias testb here"'
echo "testa echo"
echo "testa echo2"
test-pipes.sh:
function indent() {
input=$(cat)
echo "$input" | perl -p -e 's/(.*)/ \1/'
}
source test-a.sh | indent
testa
source test-b.sh > >(indent)
testb
output:
$ source test-pipes.sh
testa echo
testa echo2
test-pipes.sh:10: command not found: testa
testa echo
testa echo2
alias testb here
Piping doesn't allow the alias to be set in the current process, but the redirection does.
Can someone give a simple explanation?
From the bash man page:
Each command in a pipeline is executed as a separate process (i.e., in a sub‐shell).
Many things child processes do are isolated from the parent. Among the list are: changing the current directory, setting shell variables, setting environment variables, and aliases.
$ alias foo='echo bar' | :
$ foo
foo: command not found
$ foo=bar | :; echo $foo
$ export foo=bar | :; echo $foo
$ cd / | :; $ pwd
/home/jkugelman
Notice how none of the changes took effect. You can see the same thing with explicit subshells:
$ (alias foo='echo bar')
$ foo
foo: command not found
$ (foo=bar); echo $foo
$ (export foo=bar); echo $foo
$ (cd /); pwd
/home/jkugelman
Redirections, on the other hand, do not create subshells. They merely change where the input and output of a command go. The same goes with function calls. Functions are executed in the current shell, no subshell, so they're able to create aliases.

macOS Sierra: ${TAIL} is not working in zsh

I was trying to execute some bash scripts in zsh (oh-my-zsh). I found ${TAIL} is not working in zsh.
bash:
bash-3.2$ ${CD} /tmp; echo "test" >> test.txt; ${TAIL} test.txt
bash: /tmp: is a directory
test
zsh:
~ ${CD} /tmp; echo "test" >> test.txt; ${TAIL} test.txt
zsh: command not found: tail -f
✘ /tmp
But using tail directly is fine
✘ /tmp tail -f test.txt
test
test
whereis tail
/usr/bin/tail
echo $PATH
/usr/local/bin:/usr/bin
I think this is a classic case in zsh for Why does $var where var="foo bar" not do what I expect?
Unlike bash, by default, zsh does not split into words when passed to a command or used in a loop as for foo in $var.
var="foo bar"
enabled the flag manually as
setopt shwordsplit
then try the same as
echo "test" >> test.txt; ${TAIL} test.txt

Regression: Exported Bash function lost after going through another process

When moving from Ubuntu 14.04 to 16.04, I've noticed several of my Bash scripts failing due to missing exported functions. I wonder whether this is related to the fixes for the Shellshock bug, even though I simply export -f the functions, and not relying on the Bash-internal function representation. The failure does not occur in a direct Bash subshell, only if there's another process in between. For example, Bash invoking awk / Perl / Vim invoking another Bash. Here's an example with Perl:
Good
$ foo() { echo "foobar"; }
$ export -f foo
$ export -f; foo
foo ()
{
echo "foobar"
}
declare -fx foo
foobar
$ bash -c "export -f; foo"
foo ()
{
echo "foobar"
}
declare -fx foo
foobar
$ perl -e 'system("bash -c \"export -f; foo\"")'
foo ()
{
echo "foobar"
}
declare -fx foo
foobar
$ echo $BASH_VERSION
4.3.11(1)-release
Bad
$ foo() { echo "foobar"; }
$ export -f foo
$ export -f; foo
foo ()
{
echo "foobar"
}
declare -fx foo
foobar
$ bash -c "export -f; foo"
foo ()
{
echo "foobar"
}
declare -fx foo
foobar
$ perl -e 'system("bash -c \"export -f; foo\"")'
bash: foo: command not found
$ echo $BASH_VERSION
4.3.42(1)-release
Am I doing something wrong, or is this a bug?
Edit: #chepner pointed out that Bash uses specially-named shell identifiers to store the functions. When going through dash (0.5.8-2.1ubuntu2, working with 0.5.7-4ubuntu1), these identifiers are removed. With ksh, they are kept alive. I checked with
$ dash
$ sudo strings /proc/$$/environ | grep foo # Still passed from Bash to Dash
BASH_FUNC_foo%%=() { echo "foobar"
$ bash
$ sudo strings /proc/$$/environ | grep foo # But went missing from Dash to Bash
$ exit
$ exit
$ ksh
$ sudo strings /proc/$$/environ | grep foo
BASH_FUNC_foo%%=() { echo "foobar"
$ bash
$ sudo strings /proc/$$/environ | grep foo # Kept from Ksh to Bash
BASH_FUNC_foo%%=() { echo "foobar"
Likewise, the behavior of Vim can be changed via :set shell=/bin/bash / :set shell=/bin/ksh
So, is dash to blame?!
TL;DR: Known dash problem; gray area, might be fixed; better not rely on exports surviving a non-bash parent.
This is caused by a change in dash 0.5.8; cp. dash removes exported bash functions from the environment.
There is no consensus yet whether this will be fixed. POSIX seems to allow the stripping of invalid environment entries, other (more obscure) shells apparently do this as well, but it causes problems in various applications, especially because /bin/sh is symlinked to dash (and therefore the default shell) in Ubuntu.
My personal use case is some short utility functions that I've put in my ~/.profile, and which I reference in some shell scripts. One of those runs in an autostarted Conky daemon, and that one misses the functions because the autostart happens through dash. I can work around this. The FPATH autoload mechanism from Korn shell would be nice to have in Bash, too...
This is not ideal, but you can define your functions inside of Dash:
$ foo() { echo "foobar"; }
$ dash -c "$(declare -f); foo"
foobar

Using backticks and pipes in Ruby

This bug appears when using shell commands in a ruby script. The commands are not executed as what would happen in the bash terminal.
The first two commands are reasonable, the third is not.
`echo foo` # => "foo\n"
`echo -n foo` # => "foo"
`echo -n foo | cat` # => "-n foo\n"
In bash, we would have the following:
$ echo foo # => "foo\n"
$ echo -n foo # => "foo"
$ echo -n foo | cat # => "foo"
Is there a way I am messing up the parameter passing to the calls to echo in the Ruby commands? If you're unfamiliar, the command echo returns the string it is given. Without the -n flag, it appends a newline character, when you add the -n, it does not. cat repeats whatever it is given.
First, Ruby uses /bin/sh, not bash, for the back tick operator. It is likely that on your system, /bin/sh is a link to /bin/bash, but bash behaves differently when invoked as sh, for better POSIX compliance. However, it's not perfect, as your example shows. The POSIX specification states that echo "shall not support any options", but leaves handling of an initial argument -n to be implementation-specific.
In a quick test with /bin/sh as a link to /bin/bash,
bash -c "echo -n foo" and bash -c "echo -n foo | cat" produced identical, newline-free results, but sh -c "echo -n foo" and sh -c "echo -n foo | cat" showed the results you report. (I am not sure how other shells, such as ksh, dash, or zsh, behave when invoked as sh, but by default they all treat -n as a newline suppressor.)
For predictable results, use printf instead, which never prints a newline unless a \n is included in the format string.
printf 'foo\n'
printf 'foo'
printf 'foo' | cat
UPDATE: This appears to be a bug in bash 3.2 that was fixed at some point in the 4.x series. With 4.1 or later, sh -c "echo -n foo | cat and sh -c "echo -n foo" produce the same output.

Resources