How is the command suggestion implemented in the bash shell? - bash

gk#Jarvis:~$ sudi
No command 'sudi' found, did you mean:
Command 'sudo' from package 'sudo-ldap' (universe)
Command 'sudo' from package 'sudo' (main)
sudi: command not found
I have currently implemented a simple 'Did you mean..?' for plain English words which works as follow:
If user enters 'Kack', check the alphabets around every alphabet in the word on a QWERTY keyboard and substitute them one by one. (e.g. here they would be J,L,M,I,O for 'K'; Q,W,S,Z,X for 'a' and so on)
Return the word with the most probability (the user entered word itself too if that is the case) as the most likely word based on training on a corpus of text.
How is the code-suggestion implemented in the linux command line?

bash does not implement the suggestion logic; it is in a function defined as part of your bash initialization file, and it was put there by your distribution (Ubuntu/Debian, at a guess).
bash provides the mechanism for implementating such a function: when it attempts to execute a command, and the command is not found, it invokes the function command_not_found_handle, if it is defined.
On my machine (an Ubuntu variant), that function is defined as follows:
$ type command_not_found_handle
command_not_found_handle is a function
command_not_found_handle ()
{
if [ -x /usr/lib/command-not-found ]; then
/usr/lib/command-not-found -- "$1";
return $?;
else
if [ -x /usr/share/command-not-found/command-not-found ]; then
/usr/share/command-not-found/command-not-found -- "$1";
return $?;
else
printf "%s: command not found\n" "$1" 1>&2;
return 127;
fi;
fi
}
(And /usr/lib/command-not-found exists, is executable, and is a Python script.)

From the Bash man page:
COMMAND EXECUTION
[…]
If the name is neither a shell function nor a builtin, and contains no slashes, bash searches each element of the PATH for a directory containing an executable file by that name. Bash uses a hash table to remember the full pathnames of executable files (see hash under SHELL BUILTIN COMMANDS below). A full search of the directories in PATH is performed only if the command is not found in the hash table. If the search is unsuccessful, the shell searches for a defined shell function named command_not_found_handle. If that function exists, it is invoked with the original command and the original command's arguments as its arguments, and the function's exit status becomes the exit status of the shell. If that function is not defined, the shell prints an error message and returns an exit status of 127.
Let's try this out:
$ foobar
bash: foobar: command not found
$ function command_not_found_handle { echo "I'm so sorry, what is '$1'?"; }
$ foobar
I'm so sorry, what is 'foobar'?
Your shell initialization code might install a more useful command_not_found_handle. You would typically find such code in the system-wide configuration in /etc/bash.bashrc or a file sourced by it. Your distribution might install a handler there to invoke an external program that queries the distribution's package manager for the command or “similar” commands. For your Ubuntu, this would be implemented in the command-not-found package.
The default configuration files shipped by distributions are usually kept very general so the function might check whether the command-not-found binary is installed and, if so, call it or otherwise print a simple error message.
function command_not_found_handle {
if [ -x /usr/bin/command-not-found ]
then
/usr/bin/command-not-found "$1"
else
echo "$1: Command not found" >&2
return 127
fi
}
This way, the configuration file does not have to be changed if the command-not-found package is installed or removed again later.
I don't know how that program for Ubuntu is implemented but typically, such a tool would have a list of all known commands and find the most similar one. It might then check whether that program is installed and, if not, check what package provides it and suggest installing that.
Searching for “similar text” is usually done by computing the edit distance between two strings. Taking into account how likely mistyping a given letter is, given the current keyboard layout, would be a very smart addition.

Related

Sourcing a .sh file under zsh : addvar:1: bad substitution error message

I have the following .sh file that I would like to be able to source under zsh, which is my default shell. The beginning of the file is (after there are only export variable = path) :
function addvar () {
local tmp="${!1}" ;
tmp="${tmp//:${2}:/:}" ; tmp="${tmp/#${2}:/}" ; tmp="${tmp/%:${2}/}" ;
export $1="${2}:${tmp}" ;
}
At the execution : $ source file.sh
I get the following error message :
addvar:1: bad substitution
Could anyone see what's wrong ?
ps : I tried to put directly at the top : #!/bin/bash and execute directly by $ ./file.sh
but it doesn't do anything (none variables exported).
I have the following .sh file that I would like to be able to source under zsh, which is my default shell.
That filename extension suggests that the file is intended for a Bourne-family shell, but it says nothing about which one, and various members of the family have various incompatibilities with each other. If there is a shebang line (a line beginning with #! and appearing as the first line of the file) then it may give a more specific indication of the shell for which the file is intended, but that has functional relevance only when the file is executed directly, not when it is sourced.
I get the following error message :
addvar:1: bad substitution
Could anyone see what's wrong ?
The problem is with
local tmp="${!1}" ;
, and specifically with ${!1}. In Bash, this is an indirect variable reference, which expands to the value of the variable named by the expansion of ${1}. In Zsh, however, it is simply invalid, so you cannot use that code as-is in that shell.
It looks like the function is supposed to add the value specified as its second argument to the path-like value of the environment variable named by its first argument, removing any previous appearances of that value. Since you cannot use it as-is in Zsh, I would suggest just not. You might instead use a function that computes the appropriate updated value, and leaves the reassignment to the caller. Example:
add_path_element() {
local tmp="${1//:${2}:/:}"
tmp="${tmp/#${2}:/}"
tmp="${tmp/%:${2}/}"
echo -n "${tmp}:${2}"
}
# Instead of "addvar PATH /dir/to/add":
export PATH=$(add_path_element "$PATH" /dir/to/add)
It should also be possible to implement a version of the original addvar function that works in Zsh (and Bash) by use of the eval built-in, but I cannot recommend that approach to you because eval is very dangerous. I mention it here just in case someone else suggests it. As a basic rule, do not use eval until you are sufficiently expert at shell programming to thoroughly understand why you should not use eval.

Mac OSx terminal : "not a valid identifier" on function definition

I have a bash script in which I define the below function,
function start-if-exists()
{
if [ "`docker container ls -a|grep $1`" ]; then
echo "Container $1 exists. Starting $1..."
return `docker start $1`
else
echo "Container $1 doesn't exists."
return ""
fi
}
While executing the above function in terminal(zsh) directly I am not getting any error. But when I execute it using sh command(sh my_script.sh), I am getting the below error.
my_script.sh: line 10: `start-if-exists': not a valid identifier
where my_script.sh is the name of file.
What am I missing that my script works with zsh but fails in sh?
/bin/sh is bash, but, when started as /bin/sh, it starts in POSIX mode. According to the bash man page, in POSIX mode:
Function names must be valid shell `name's. That is, they may not
contain characters other than letters, digits, and underscores, and
may not start with a digit. Declaring a function with an invalid
name causes a fatal syntax error in non-interactive shells.
A note about how to figure things like this out:
At the Terminal command line, I executed /bin/sh --version to see information about it. It printed “GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)”.
Then I referred to the bash man page, using the command man bash. Since that is long, you might prefer to save a copy to a file and view it in your preferred text editor. The raw man output includes archaic underscores and backspaces. You can get a copy without these by executing man bash | col -b > file.txt.
In the man page, I searched for ”sh” (as a complete word, not a raw search for those letters, since they appear as parts of many unrelated words). This quickly revealed discussion that bash behaves differently when started as “sh”; it starts in POSIX mode.
Searching further for “POSIX” revealed a list of things that are different in POSIX mode.

Run scripts without typing the extension

I use git-bash on windows.
Is there a way to make it run windows batches (.cmd) without typing file extensions?
And the same question about *.sh files. I prefer putting bash scripts into .sh to distinguish them in the explorer easier.
PS. Aliases or proxy-scripts without extension are not welcomed.
Hopefully, there is some geek's way to do this. So, still looking for an answer...
You could any flavour of bash (cmder, git-bash, ms bash, cygwin, gnu-utilities…) which will let you use any unix command without extension, even if stored as a .exe.
Take attention to the CR/LF, and use dos2unix at each shell script creation to prevent misinterpretation of line ending.
You won't be able to use .sh file without extension… but to create an alias for each script :
$ alias myscript="/usr/local/bin/myscript.sh"
Put them in your ~/.bashrc
First of all a bit of content about file extension:
A filename extension is an identifier specified as a suffix to the
name of a computer file. The extension indicates a characteristic of
the file contents or its intended use. A file extension is typically
delimited from the filename with a full stop (period), but in some
systems it is separated with spaces.
Some file systems implement filename extensions as a feature of the
file system itself and may limit the length and format of the
extension, while others treat filename extensions as part of the
filename without special distinction.
Filesystems for UNIX-like operating systems (opposed to DOS/Windows) do not separate the
extension metadata from the rest of the file name. The dot character
is just another character in the main filename, and filenames can have
multiple extensions, usually representing nested transformations, ...
Regarding your shell scripts:
So basically in Unix file extension are not important/not mandatory so you can directly omit them. If for any reason you want to keep them (and I believe that you should) then you can define an alias to them. (refer to https://askubuntu.com/questions/17536/how-do-i-create-a-permanent-bash-alias)
You must also keep in mind the EOL char ('\n' vs '\r\n' that differ between Unix and Windows.
Regarding your windows batches, you can not run them directly in a Unix like environment so you will not be able to run them at the same time from your git bash except if you use a tool like GH4W (github generate ssh key on windows) or use git-cmd.bat (What is the exact meaning of Git Bash?)
Maybe I've found a solution that requires some additional coding. Instead of insulting the user when typing a non-existent command, you could try to rewrite the code so it executes a command by adding '.sh' or '.cmd'.
GitGub: insult-linux-unix-bash-user-when-typing-wrong-command
Also do a search for the command_not_found_handle. This is available on most Linux systems and might or might not be available for git-bash.
This is something I was looking for myself and following on from FithAxiom's answer, have found a solution. It feels ugly, because it basically means overriding the "command not found" handling, and searching for the file ourselves. But it does achieve the desired effect.
This is a modification of an answer given in another thread. You add this to your .bashrc file. You can modify it to suit your needs (and improvements are also welcome).
command_not_found_handle()
{
cmd=$1
shift
args=( "$#" )
IFS=:
for dir in $PATH; do
if [ -f $dir/$cmd.cmd ]; then { "$dir/$cmd.cmd" "${args[#]}"; return; }
elif [ -f $dir/$cmd.ps1 ]; then { powershell.exe -file "$dir/$cmd.ps1" "${args[#]}"; return; }
elif [ -f $dir/$cmd.sh ]; then { "$dir/$cmd.sh" "${args[#]}"; return; }
fi
done
# Replicate standard "command not found" error
echo "bash: $1: command not found" >&2
return 127
}
You can create a file ~/.bashrc and override the command not found handler. Bash let you handle it. Read it for more info.
In .bashrc file you can update the command_not_found_handle function provided by bash.
command_not_found_handle() {
unset -f command_not_found_handle
command="$1".sh
shift
exec "$command" "$#"
}

Purpose of #!/bin/false in bash script

While working on a project written in bash by my former colleague, I noticed that all .sh files contain nothing but function definitions start with #!/bin/false, which is, as I understand, a safety mechanism of preventing execution of include-only files.
Example:
my_foo.sh
#!/bin/false
function foo(){
echo foontastic
}
my_script.sh
#!/bin/bash
./my_foo.sh # does nothing
foo # error, no command named "foo"
. ./my_foo.sh
foo # prints "foontastic"
However when I don't use #!/bin/false, effects of both proper and improper use are exactly the same:
Example:
my_bar.sh
function bar(){
echo barvelous
}
my_script.sh
#!/bin/bash
./my_bar.sh # spawn a subshell, defines bar and exit, effectively doing nothing
bar # error, no command named "bar"
. ./my_bar.sh
bar # prints "barvelous"
Since properly using those scripts by including them with source in both cases works as expected, and executing them in both cases does nothing from the perspective of a parent shell and generate no error message concerning invalid use, what is exactly the purpose of #!/bash/false in those script?
In general, let’s consider a file testcode with bash code in it
#!/bin/bash
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
echo "You are executing ${BASH_SOURCE[0]}"
else
echo "You are sourcing ${BASH_SOURCE[0]}"
fi
you can do three different things with it:
$ ./testcode
You are executing ./testcode
This works if testcode has the right permissions and the right shebang. With a shebang of #!/bin/false, this outputs nothing and returns a code of 1 (false).
$ bash ./testcode
You are executing ./testcode
This completely disregards the shebang (which can even be missing) and it only requires read permission, not executable permission. This is the way to call bash scripts from a CMD command line in Windows (if you have bash.exe in your PATH...), since there the shebang machanism doesn’t work.
$ . ./testcode
You are sourcing ./testcode
This also completely disregards the shebang, as above, but it is a complete different matter, because sourcing a script means having the current shell execute it, while executing a script means invoking a new shell to execute it. For instance, if you put an exit command in a sourced script, you exit from the current shell, which is rarely what you want. Therefore, sourcing is often used to load function definitions or constants, in a way somewhat resembling the import statement of other programming languages, and various programmers develop different habits to differentiate between scripts meant to be executed and include files to be sourced. I usually don’t use any extension for the former (others use .sh), but I use an extension of .shinc for the latter. Your former colleague used a shebang of #!/bin/false and one can only ask them why they preferred this to a zillion other possibilities. One reason that comes to my mind is that you can use file to tell these files apart:
$ file testcode testcode2
testcode: Bourne-Again shell script, ASCII text executable
testcode2: a /bin/false script, ASCII text executable
Of course, if these include files contain only function definitions, it’s harmless to execute them, so I don’t think your colleague did it to prevent execution.
Another habit of mine, inspired by the Python world, is to place some regression tests at the end of my .shinc files (at least while developing)
... function definitions here ...
[ "$0" != "${BASH_SOURCE[0]}" ] && return
... regression tests here ...
Since return generates an error in executed scripts but is OK in sourced scripts, a more cryptic way to get the same result is
... function definitions here ...
return 2>/dev/null || :
... regression tests here ...
The difference in using #!/bin/false or not from the point of view of the parent shell is in the return code.
/bin/false always return a failing return code (in my case 1, but not sure if it is standard).
Try that :
./my_foo.sh //does nothing
echo $? // shows "1", a.k.a failing
./my_bar.sh //does nothing
echo $? // shows "0", a.k.a. everything went right
So, using #!/bin/false not only documents the fact that the script is not intended to be executed, but also produces an error return code when doing so.

'have' keyword for bash completion

Is have a keyword in bash? Or do bash completion scripts use a language that is not bash?
have gcc &&
_gcc()
{
It is common. See: grep "have .* &&" /etc/bash_completion.d/*
I could not find any information on the bash completion tutorials I've seen and I could not find any information in man bash. It's also difficult to google "have". Where do I find documentation on this?
I'm guessing it has to do with making sure that there gcc exists in the PATH?
edit: yes. /etc/bash_completion contains:
have()
{
unset -v have
# Completions for system administrator commands are installed as well in
# case completion is attempted via `sudo command ...'.
PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin type $1 &>/dev/null &&
have="yes"
}
have and _have are just two functions defined in the base bash_completion file. Between the two, they form a wrapper around the built-in type command to determine if a particular command/program available.
# This function checks whether we have a given program on the system.
#
_have()
{
# Completions for system administrator commands are installed as well in
# case completion is attempted via `sudo command ...'.
PATH=$PATH:/usr/sbin:/sbin:/usr/local/sbin type $1 &>/dev/null
}
# Backwards compatibility for compat completions that use have().
# #deprecated should no longer be used; generally not needed with dynamically
# loaded completions, and _have is suitable for runtime use.
have()
{
unset -v have
_have $1 && have=yes
}

Resources