Bash script select option based on value - bash

I have a command I would like to run in terminal.
eg.
samplecommand -s
Which provides an option later on:
Option 1
Option 2
with the input question
Please enter an index:
So I would need to input 2 into terminal.
However, the order of Option 1 and Option 2 change making it hard to hard code a specific index.
e.g. it could be:
Option 2
Option 1
Is there a way to use the context of options provided and make the script select only "Option 2".

You can use se;ect:
> select: select NAME [in WORDS ... ;] do COMMANDS; done
> The WORDS are expanded, generating a list of words. The
> set of expanded words is printed on the standard error, each
> preceded by a number. If `in WORDS' is not present, `in "$#"'
> is assumed. The PS3 prompt is then displayed and a line read
> from the standard input. If the line consists of the number
> corresponding to one of the displayed words, then NAME is set
> to that word. If the line is empty, WORDS and the prompt are
> redisplayed. If EOF is read, the command completes. Any other
> value read causes NAME to be set to null. The line read is saved
> in the variable REPLY. COMMANDS are executed after each selection
> until a break command is executed.

Related

Search in logfile - exit code

I have some legacy softwares that I need to automate under Control-M. These job are under Windows 2008R2.
These jobs have an exit code 0 if they run ok, but also if they can manage some errors. I need to raise an alarm when a specific string is in the log.
The string is not in the output of executable.
I implemented another job for this. It goes to search a string inthe file and in "On Do Actions" I search for the statement.
To have the statement in the output I think to use something like a grep. I used:
findstr
findstr "myerrorcode" D:\Log\greptest_%%$ODATE..log
grep under cygwin
In both case I have an identical situation:
If the string is found, everythings is ok
If the file is not found or cannot be open, grep or findstr return an exit code = 1. This is ok, because the job has to raise an error.
But the problem is: when the string is not found in the file, both grep and findstr have a return code = 1.
How can I discriminate the cases when the file cannot be open and when everything runs ok, but the sring in the log is not found?
You should be able to use grep's exit status to detect the reason for failure. According to the POSIX grep docs, exit status section:
EXIT STATUS
The following exit values shall be returned:
0 One or more lines were selected.
1 No lines were selected.
>1 An error occurred.
It's similar for GNU grep (consistent, but more specific):
Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. [...] Other grep implementations may exit with status greater than 2 on error.
For example, in bash, you could use the case command to handle multiple branches like this:
#!/bin/bash
# search for error code in file
grep code file
# store the exit status in variable err
err=$?
# test several cases
case $err in
0) echo All good.;;
1) echo Code not found.;;
*) echo Error reading from file.;;
esac
You can handle this within Control-M easily:
Add in Job Tab "Action"
Add "On Do Action"
On: "Specific statement output"
Statement (as Control-M documentation state):
A character string, from 1 through 132 characters in length, containing a statement from the job script file The specified string can be a portion of the statement.
Statement character strings can each contain mask characters. Valid
mask characters are:
* – represents any number of characters (including no characters)
$ – represents any single character
? – represents any single character
Code:
A character string, from 1 through 255 characters in length, to be
compared to the operating system’s response to the specified
statement.
Code character strings can each contain mask characters. Valid mask
characters are:
* – represents any number of characters (including no characters)
$ – represents any single character
? – represents any single character
Example:
On Do Action Control-M 8

Using case statement within select loop

I am currently doing bash programming wherein I am using case statement within select loop.However,whenever I enter any input, the control goes to default case and not the case which it should actually go to.For eg. when I enter date,instead of going to date case, it goes to default case. Can anybody of you notice some error?
#!/bin/bash
select command in date pwd ls
do
case $command in
date)date;;
pwd)pwd;;
ls)ls;;
*)echo"wrong";;
esac
done
[...] when I enter date,instead of going to date case, it goes to default case.
The select builtin works with numbered options. You need to enter 1 for the first option "date", 2 for the second option "pwd", and so on.
The relevant parts from help select:
select: select NAME [in WORDS ... ;] do COMMANDS; done
[...] If the line consists of the number
corresponding to one of the displayed words, then NAME is set
to that word. If the line is empty, WORDS and the prompt are
redisplayed. If EOF is read, the command completes. Any other
value read causes NAME to be set to null. [...]

Customizing bash completion output: each suggestion on a new line

When you type something, you often use bash autocompletion: you start writing a command, for example, and you type TAB to get the rest of the word.
As you have probably noticed, when multiple choices match your command, bash displays them like this :
foobar#myserv:~$ admin-
admin-addrsync admin-adduser admin-delrsync admin-deluser admin-listsvn
admin-addsvn admin-chmod admin-delsvn admin-listrsync
I'm looking for a solution to display each possible solution on a new line, similar to the last column on a ls -l. Ever better, it would be perfect if I could apply a rule like this: "if you find less than 10 suggestions, display them one by line, if more => actual display".
bash prior to version 4.2 doesn't allow any control over the output format of completions, unfortunately.
Bash 4.2+ allows switching to 1-suggestion-per-line output globally, as explained in Grisha Levit's helpful answer, which also links to a clever workaround to achieve a per-completion-function solution.
The following is a tricky workaround for a custom completion.
Solving this problem generically, for all defined completions, would be much harder (if there were a way to invoke readline functions directly, it might be easier, but I haven't found a way to do that).
To test the proof of concept below:
Save to a file and source it (. file) in your interactive shell - this will:
define a command named foo (a shell function)
whose arguments complete based on matching filenames in the current directory.
(When foo is actually invoked, it simply prints its argument in diagnostic form.)
Invoke as:
foo [fileNamePrefix], then press tab:
If between 2 and 9 files in the current directory match, you'll see the desired line-by-line display.
Otherwise (1 match or 10 or more matches), normal completion will occur.
Limitations:
Completion only works properly when applied to the LAST argument on the command line being edited.
When a completion is actually inserted in the command line (once the match is unambiguous), NO space is appended to it (this behavior is required for the workaround).
Redrawing the prompt the first time after printing custom-formatted output may not work properly: Redrawing the command line including the prompt must be simulated and since there is no direct way to obtain an expanded version of the prompt-definition string stored in $PS1, a workaround (inspired by https://stackoverflow.com/a/24006864/45375) is used, which should work in typical cases, but is not foolproof.
Approach:
Defines and assigns a custom completion shell function to the command of interest.
The custom function determines the matches and, if their count is in the desired range, bypasses the normal completion mechanism and creates custom-formatted output.
The custom-formatted output (each match on its own line) is sent directly to the terminal >/dev/tty, and then the prompt and command line are manually "redrawn" to mimic standard completion behavior.
See the comments in the source code for implementation details.
# Define the command (function) for which to establish custom command completion.
# The command simply prints out all its arguments in diagnostic form.
foo() { local a i=0; for a; do echo "\$$((i+=1))=[$a]"; done; }
# Define the completion function that will generate the set of completions
# when <tab> is pressed.
# CAVEAT:
# Only works properly if <tab> is pressed at the END of the command line,
# i.e., if completion is applied to the LAST argument.
_complete_foo() {
local currToken="${COMP_WORDS[COMP_CWORD]}" matches matchCount
# Collect matches, providing the current command-line token as input.
IFS=$'\n' read -d '' -ra matches <<<"$(compgen -A file "$currToken")"
# Count matches.
matchCount=${#matches[#]}
# Output in custom format, depending on the number of matches.
if (( matchCount > 1 && matchCount < 10 )); then
# Output matches in CUSTOM format:
# print the matches line by line, directly to the terminal.
printf '\n%s' "${matches[#]}" >/dev/tty
# !! We actually *must* pass out the current token as the result,
# !! as it will otherwise be *removed* from the redrawn line,
# !! even though $COMP_LINE *includes* that token.
# !! Also, by passing out a nonempty result, we avoid the bell
# !! signal that normally indicates a failed completion.
# !! However, by passing out a single result, a *space* will
# !! be appended to the last token - unless the compspec
# !! (mapping established via `complete`) was defined with
# !! `-o nospace`.
COMPREPLY=( "$currToken" )
# Finally, simulate redrawing the command line.
# Obtain an *expanded version* of `$PS1` using a trick
# inspired by https://stackoverflow.com/a/24006864/45375.
# !! This is NOT foolproof, but hopefully works in most cases.
expandedPrompt=$(PS1="$PS1" debian_chroot="$debian_chroot" "$BASH" --norc -i </dev/null 2>&1 | sed -n '${s/^\(.*\)exit$/\1/p;}')
printf '\n%s%s' "$expandedPrompt" "$COMP_LINE" >/dev/tty
else # Just 1 match or 10 or more matches?
# Perform NORMAL completion: let bash handle it by
# reporting matches via array variable `$COMPREPLY`.
COMPREPLY=( "${matches[#]}" )
fi
}
# Map the completion function (`_complete_foo`) to the command (`foo`).
# `-o nospace` ensures that no space is appended after a completion,
# which is needed for our workaround.
complete -o nospace -F _complete_foo -- foo
bash 4.2+ (and, more generally, applications using readline 6.2+) support this with the use of the completion-display-width variable.
The number of screen columns used to display possible matches when performing completion. The value is ignored if it is less than 0 or greater than the terminal screen width. A value of 0 will cause matches to be displayed one per line. The default value is -1.
Run the following to set the behavior for all completions1 for your current session:
bind 'set completion-display-width 0'
Or modify your ~/.inputrc2 file to have:
set completion-display-width 0
to change the behavior for all new shells.
1 See here for a method for controlling this behavior for individual custom completion functions.
2 The search path for the readline init file is $INPUTRC, ~/.inputrc, /etc/inputrc so modify the file appropriate for you.

bash dynamic dialog

I have a task to write simple bash script that adds deletes and views entries from file.
The requirement is to use "dialog"
data structure in file:
Name Surname mymail#mail.com
Another New person#database.loc
basically i have accomplished everything except delete, i know how to do the delete itself(with "sed" i think?)
But i need to use dialog --menu to display the search results.
The menu item should be whole line of text i think as after selection of an item i will use "grep" again to filter out the unique entry.
Maybe anyone can put me on the right direction?
Thanks.
I used dialog never before, but maybe I still can help. Try this:
declare -a args=()
while read
do
args+=("$REPLY" "")
done < <( grep '#' example.txt )
dialog --menu "Please select the line you want to edit" 40 60 34 "${args[#]}"
How does this work?
dialog --menu takes the following arguments:
question text
height and width of the window
height of the menu (which should be 7 less then the window height to use it fully, in my experience)
pairs of tag string and description.
The selected tag string is then output (on stderr) at the end.
How to create such a list strings from our grep output? A failed try is described below, here the working one.
The read command reads one line a time from standard input (to which we redirected the grep output), and puts it (if we don't give other options or arguments) in the REPLY variable.
We then add this value (quoted to be one element) to the array args , and additionally a single "" to add an empty string to the array, too.
We have to use the < <( ... ) syntax for redirection, since the normal | creates a subshell for the second command, which has the effect that changes to the variables are not propagated back to the original shell. (< means read input from file, and <( ... ) creates a pipe to read the output of the command and results in its filename.)
Then we use the "${args[#]}" parameter expansion - # has the effect that each element is individually quoted as the result. So for your example, the command line now looks like
dialog --menu "Please select the line you want to edit" 40 60 34 "Name Surname mymail#mail.com" "" "Another New person#database.loc" ""
This creates a two line menu, with the complete lines as the "tag", and an empty string as the clarification.
You will need some way to capture it's standard error output, as it puts the result there.
Another idea which does not work:
The question is how to to get the output of grep in the command line of dialog, so that it forms two arguments for each line.
What helps here are the following syntactic constructs:
Command substitution: $( cmd ) executes the command and converts the result to a string, which is then used at the point in the command line.
So, we need some command which produces two "words" for each line of grep output (since your file would give three words). As you are already using sed, why not use it here too?
The sed command s/^.*$/"&" ""/ replaces each line with the line enclosed in "", followed by another two quotes.
"Name Surname mymail#mail.com" ""
"Another New person#database.loc" ""
The idea would now be to use
dialog --menu "Please select the line you want to edit" 40 60 34 $( sed -e 's/^.*$/"&" ""/' < input )
but unfortunately the word-splitting of bash does not respect "" after command-substitution, so bash gives the six arguments "Name, Surname, mymail#mail.com", "", "Another, New, person#database.loc" and "" to the dialog program. (In fact, using "" to inhibit splitting seems to work only for quotes given literal in the source or in eval - but eval does not work here since we have line breaks, too.)

How to use arguments from previous command?

I know that Esc + . gives you the last argument of the last command.
But I'm interested in first argument of the last command.
Is there a key binding to do so?
On the same lines, is there a generic way of getting the nth argument from the last command?
I know that in a bash script, you can use $0, $1 etc., but these don't work on the commandline.
Also, what about iterating through the 0th argument of previous commands, like we can do with the last argument by continuously pressing Esc + .?
!$ gets the last element of the previous command line argument.
Just as M-. (meta-dot or esc-dot or alt-dot) is the readline function yank-last-arg, M-C-y (meta-control-y or esc-ctrl-y or ctrl-alt-y) is the readline function yank-nth-arg. Without specifying n, it yanks the first argument of the previous command.
To specify an argument, press Escape and a number or hold Alt and press a number. You can do Alt--to begin specifying a negative number then release Alt and press the digit (this will count from the end of the list of arguments.
Example:
Enter the following command
$ echo a b c d e f g
a b c d e f g
Now at the next prompt, type echo (with a following space), then
Press Alt-Ctrl-y and you'll now see:
$ echo a
without pressing Enter yet, do the following
Press Alt-3 Alt-Ctrl-y
Press Alt-- 2 Alt-Ctrl-y
Now you will see:
$ echo ace
By the way, you could have put the echo on the line by selecting argument 0:
Press Alt-0 Alt-Ctrl-y
Edit:
To answer the question you added to your original:
You can press Alt-0 then repeatedly press Alt-. to step through the previous commands (arg 0). Similarly Alt-- then repeating Alt-. would allow you to step through the previous next-to-last arguments.
If there is no appropriate argument on a particular line in history, the bell will be rung.
If there is a particular combination you use frequently, you can define a macro so one keystroke will perform it. This example will recall the second argument from previous commands by pressing Alt-Shift-Y. You could choose any available keystroke you prefer instead of this one. You can press it repeatedly to step through previous ones.
To try it out, enter the macro at a Bash prompt:
bind '"\eY": "\e2\e."'
To make it persistent, add this line to your ~/.inputrc file:
"\eY": "\e2\e."
Unfortunately, this doesn't seem to work for arg 0 or negative argument numbers.
To use the first argument, you can use !^ or !:1
Example:
$ echo a b c d e
a b c d e
$ echo !^
echo a
a
$ echo a b c d e
a b c d e
$ echo !:1
echo a
a
Since your question is about using any other arguments, here are some useful ones:
!^ first argument
!$ last argument
!* all arguments
!:2 second argument
!:2-3 second to third arguments
!:2-$ second to last arguments
!:2* second to last arguments
!:2- second to next to last arguments
!:0 the command
!! repeat the previous line
The first four forms are more often used. The form !:2- is somewhat counter-intuitive, as it doesn't include the last argument.
I liked #larsmans answer so much I had to learn more. Adding this
answer to help others find the man page section and know what to
google for:
$ man -P 'less -p ^HISTORY\ EXPANSION' bash
<...>
Word Designators
Word designators are used to select desired words from the event.
A : separates the event specification from the word designator.
It may be omitted if the word designator begins with a ^, $, *, -,
or %. Words are numbered from the beginning of the line, with the
first word being denoted by 0 (zero). Words are inserted into the
current line separated by single spaces.
0 (zero)
The zeroth word. For the shell, this is the command word.
n The nth word.
^ The first argument. That is, word 1.
$ The last argument.
% The word matched by the most recent ‘?string?’ search.
x-y A range of words; ‘-y’ abbreviates ‘0-y’.
* All of the words but the zeroth.
This is a synonym for ‘1-$’.
It is not an error to use * if there is just one word in
the event; the empty string is returned in that case.
x* Abbreviates x-$.
x- Abbreviates x-$ like x*, but omits the last word.
If a word designator is supplied without an event
specification, the previous command is used as the event.
Tested on Ubuntu 18.04
To insert previous arguments:
Alt+.: insert last argument from last command.
Alt+#+.: insert #nth last argument from last command.
Alt+- , # , Alt+., zsh: Alt+-+#+.: insert #nth first argument from last command.
In Linux you can repeat commands to go back in history
Example:
Last command is:
mv foo bar
Alt+0+.: insert first argument of last command = mv
Alt+2+.: insert last 2nd argument of last command = foo
up , Ctrl+w: last command without the last word = mv foo
General shortcuts
Ctrl+w: removes last word from cursor
Alt+d: removes next word from cursor
Ctrl+k: cuts everything after cursor
Ctrl+u, zsh: Alt+w: cuts everything before cursor
zsh: Ctrl+u: cuts the entire command (In bash you can combine Ctrl+u , Ctrl+k)
Ctrl+y: paste characters previously cut with Ctrl+u and Ctrl+k
Ctrl+_: undo last edit (very useful when exceeding Ctrl+w)
Ctrl+left: move to last word
Ctrl+right: move to next word
home or Ctrl+a: move to start of line
end or Ctrl+e: move to end of line
To iterate through the arguments in a previous command
only works in zsh
run or add this to your ~/.zshrc
autoload -Uz copy-earlier-word
zle -N copy-earlier-word
bindkey "^[:" copy-earlier-word
Now use Alt+. to go as back as you want, then use Alt+: to iterate through the arguments
Assuming last command is
echo 1 2 3 4 5
Alt+.: 5
Alt+.+:: 4
Alt+.+:+:: 3
Alt+.+:+:+:: 2
Alt+.+:+:+:+:: 1
Alt+.+:+:+:+:+:: echo
source: https://stackoverflow.com/a/34861762/3163120
To see all shortcuts available
bash: bind -lp
zsh: bindkey -L
I'm keeping this up-to-date here: https://github.com/madacol/knowledge/blob/master/bash-zsh_TerminalShorcuts.md
!^ may be the command for the first argument. i'm not sure if there is a way to get the nth.
You can also get arguments from any command in your history!
$ echo a b c d e f g
a b c d e f g
$ echo build/libs/jenkins-utils-all-0.1.jar
build/libs/jenkins-utils-all-0.1.jar
$ history | tail -5
601 echo build/libs/jenkins-utils-all-0.1.jar
602 history | tail -10
603 echo a b c d e f g
604 echo build/libs/jenkins-utils-all-0.1.jar
605 history | tail -5
$ echo !-3:4
echo d
d
$ echo !604:1
echo build/libs/jenkins-utils-all-0.1.jar
build/libs/jenkins-utils-all-0.1.jar
!^ will get you the first param, !$ will get you the last param, !:n will get you the nth element.
Basically it has a use in yanking previous (command's) arguments.
For instance, if the following command is issued:
echo Hello, world how are you today?
then, Hello, will be the first argument, and today? the sixth, that is the last one; meaning it can be referenced by typing:
Alt+6 followed by Ctrl-Alt-6
Ctrl is traditionally denoted as a hat character ^ prepended to keys names, and Alt as M- that is Meta prefix.
So the above shortcut can be redefined as ^My to yank.
Also, there is hats substitution shortcut in the command line:
echo Hello, world!
^Hello^Bye
Bye, world!
to substitute the previous command's first matched string, meaning:
Hello, world! Hello, people!
^Hello^Bye
would result in:
Bye, world! Hello, people!
leaving the second match (hello) unchanged.
Note: Do not leave space between hats, or the operation won't work.
The above is just a shortcut for:
!:s/Hello/Bye
event-level(*) substitution for the first found (matched) string in the previous command, while prefixing the first part with the g switch will apply to the whole line globally:
echo Hello, world! Hello, people!
!:gs/Hello/Bye
Bye, world! Bye, people!
as usually being done in other related commands such as sed, vi, and in regex (regular expression) - a standart way to search (match string).
No, you can't do !:sg/Hello/Bye or !:s/Hello/Bye/g here, that's the syntax!
! is for events; event might be understood as command output or operation done in the commands history.
That's what I understood by using it myself and trying things on my own from what I read from various sources including manual pages, blogs, and forums.
Hope it will shed some light into mysterious ways of bash, the Bourne-Again shell (a play on sh shell, which itself is called Bourne shell after its inventor's last name), what is default shell in many distributions including servers (server OS's).
The method described at the end of the accepted answer also works with the zeroth argument for me. I have these lines in my ~/.inputrc:
"\en": "\e0\e."
"\em": "\e1\e."
"\e,": "\e2\e."
\e2\e. has the advantage over \e2\e\C-y that it cycles through previous commands if it is pressed repeatedly instead of inserting the second argument of the previous command multiple times.
To insert the whole previous command, you can type !!\e^. \e^ is history-expand-line.
If you are on a mac you will tend to get extended characters with ctrl+letter. I have my right-of-space-bar-option key defined as meta in my terminal (iTerm2) set up. This means I use the key to navigate by word and pull parameters from previous commands.
For pasting 1th argument, press and hold down Alt key, and while it is down, hit the '1' key followed by the '.' key.
For pasting n-th argument, replace the '1' key above with the corresponding number key.
If this does not work, your terminal emulator may be catching the Alt key before it gets to shell. Some terminals (xfce4-terminal) allow turning off the "Alt-" shortcuts in the configuration file.
Credit to Jonas Eberle, I've fished this out from his comment to another answer here.

Resources