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.)
Related
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.
I have written a shell script for automating some tasks that I run from the terminal as -
v#ubuntu:$ ./automate.sh from:a1 to:a2 msg:'edited'
How can I (if at all) customize the script so as to enter each argument in a custom format on a separate line and execute it by pressing some other key to execute the shell script? So, I would do -
v#ubuntu:$ ./automate.sh
from : a1
to : a2
msg : 'next change'
... and then hit say Ctrl+Enter or F5 to execute this particular script?
NOTE : I know there is a hacky work around by simply typing ./automate.sh \ and hitting Enter after the trailing backslash to get a new line, but I was hoping to find a more elegant way to do this from within the script itself.
Also, I've purposely changed each argument to include whitespaces and the msg argument to include a string with spaces. So if anyone can point me in the right direction as to how to accomplish that as an added bonus, I'll be really grateful :)
If you know the number of arguments it is easy. Basics first.
#!/bin/bash
if [ $# == 0 ]
then
read v1 # gets onto new line. reads the whole line until ENTER
read v2 # same
read v3 # same
fi
# Parse $v1, $v2, $v3 as needed and run your script
echo ""
echo "Got |$v1|, |$v2|, |$v3|"
When you type automate.sh and hit enter the script is started, having received no arguments. With no arguments ($# == 0) the first read is executed, which prints a new line, waits, and gets the line typed in (once enter is hit) into $v1. The control goes back to the script, the next read gets the next typed line ... after the last one it drops out of if-else and continues. Parse your variables and run the script.
Session:
> automate.sh Enter
typed line Enter
more items Enter
yet more Enter
Got |typed line| |more items| |yet more|
>
You don't need Control-Enter or F5, it continues after 3 (three) lines.
This also allows you to provide both behaviors. Add an else, which will be executed if there are some arguments. You can then use the script by either supplying arguments on the first line (invocation you have so far), or in this new way.
If you need an unspecified number of arguments this approach will need more work.
Read words in input line into variables
If read is followed by variable names, like read v1 v2, then it reads each word into a variable, and the last variable gets everything that may have remained on the line. So replace read lines with
read k1 p1 s1
read k2 p2 s2
read k3 p3 s3
Now $k1 contains the first word (from), and $k2 and $k3 have the first words on their lines; then $p1 (etc) have the second word (:), and $s1 (etc) have everything else to the end of their lines (a1, a2, 'next change'). So you don't need those single quotes. All this is simple to modify if you want the script to print something on each line before input.
Based on the clarification in the comment, it is indeed desirable to not have to enter the whole strings, as one might think. This is "simple to modify"
read -p 'from :' s1
read -p 'to :' s2
read -p 'msg :' s3
Now the user only needs to enter the part after :, captured in $s variables. All else is the same.
See, for example: The section on user input in the Bash Guide; Their Advanced Guide (special variables); For a far more involved user interaction, this post. And, of course, man read.
It will be hard to bind Control-Enter in bash. If you are ok to change it for Control-D then everything might look like:
#!/usr/local/bin/bash
read -p 'From: ' from
read -p 'To: ' to
read -p 'Msg: ' msg
read keystroke
if [ "$keystroke" == "^D" ]; then
echo "$from $to $msg"
# do something else
fi
I'm new on bash and I'm trying to write a bash script that will save user's multilines inputs (a text with newlines, somes lines of code, etc.). I need to allow newline (when you press "Enter"), multiline paste (when you paste few lines "Ctrl+V") and set a new key, instead of "Enter", to validate, send the input and continue to the next step of the script.
I tried with read but you can not do multiline.
echo "Enter content :"
read content
I found an example with readarray here (How to delete a character in bash read -d multiline input?) that allow to press "Enter" for newline but each words separate by space are separate in the array. I would like to have only the lines separated.
echo "Enter package names, one per line: hit Ctrl-D on a blank line to stop"
readarray -t pkgs
Do you have any ideas ? Or there is maybe a completely different way to do it ? Thank you for your help.
You can set IFS to newline so that only newlines will separate items in the array.
IFS=$'\n' readarray lines
The first line read will be ${lines[0]}, the second ${lines[1]}, etc. ${#lines[#]} tells you how many lines, and the last one will be ${lines[${#lines[#]}-1]}.
To loop over the array, you should use "${lines[#]}", not ${lines[*]}; the latter will take you right back to looping over individual words.
A config file that the last line contains data that I want to assign everything to the RIGHT of the = sign into a variable that I can display and call later in the script.
Example: /path/to/magic.conf:
foo
bar
ThisOption=foo.bar.address:location.555
What would be the best method in a bash shell script to read the last line of the file and assign everything to the right of the equal sign? In this case, foo.bar.address:location.555.
The last line always has what I want to target and there will only ever be a single = sign in the file that happens to be the last line.
Google and searching here yielded many close but non-relative results with using sed/awk but I couldn't come up with exactly what I'm looking for.
Use sed:
variable=$(sed -n 's/^ThisOption=//p' /path/to/magic.conf)
echo "The option is: $variable")
This works by finding and removing the ThisOption= marker at the start of the line, and printing the result.
IMPORTANT: This method absolutely requires that the file be trusted 100%. As mentioned in the comments, anytime you "eval" code without any sanitization there are grave risks (a la "rm -rf /" magnitude - don't run that...)
Pure, simple bash. (well...using the tail utility :-) )
The advantage of this method, is that it only requires you to know that it will be the last line of the file, it does not require you to know any information about that line (such as what the variable to the left of the = sign will be - information that you'd need in order to use the sed option)
assignment_line=$(tail -n 1 /path/to/magic.conf)
eval ${assignment_line}
var_name=${assignment_line%%=*}
var_to_give_that_value=${!var_name}
Of course, if the var that you want to have the value is the one that is listed on the left side of the "=" in the file then you can skip the last assignment and just use "${!var_name}" wherever you need it.
I want to create a simple dialog with bash-dialog. I work with (X)DSL and bash-3.2. The latest (X)DSL is based on Linux 2.4.31 and comes with bash-2.05, however, bash-3.2 is downloadable from MyDSL/Testing. So, my script runs under '#!/bin/bash-3.2/bin/bash'.
The menu items the users can choose from come from a database.
Example database file 'armatures':
Indoor Lighting|Lighting for Indoor use
Outdoor Lighting|Lighting for Outdoor use
I retrieve the data into an array 'options' from the 'armatures' file with:
options=($(awk -F"|" '{ print $1,$2 }' armatures)
and in terminal 'echo' the array:
echo ${options[#]}
which shows:
"Indoor Armatures" "Lighting for Indoor use" "Outdoor Armatures" "Lighting for Outdoor use"
This looks OK to use as a selection menu with 'whiptail' but it isn't. The command line:
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 ${options[#]}
shows:
column1-column2
Indoor-Armatures
Lighting-for
Indoor-use
Outdoor-Armatures
Lighting-for
Outdoor-use
in stead of:
column1-column2
Indoor armatures-Lighting for Indoor use
Outdoor armatures-Ligthing for Outdoor use
It seems that array elements with double quotes are ignored or not seen by 'whiptail'. I also tried "${options[#]}" but that always results on the first word 'Indoor'.
Aside from 'whiptail' I tried 'dialog' but they are the same: version information shows 'cdialog (ComeOn Dialog!) version 1.1-20080316' in both cases.
I have very limited resources and don't want to venture (yet) into 'xdialog', 'zenity', 'dzen' and the like, even if that would solve this. I am also limited to Linux 2.4.31 due to XDSL (for XBOX).
I've been browsing the Internet a lot but to no avail. What could be the solution with 'whiptail/dialog'?
The basic problem you are having comes from the order in which the shell parses command lines: it parses (and removes) quotes and escapes before it replaces variables with their values, and it never goes back and re-parses for any quotes or escapes within the replaced values. Consider this example:
var='"foo bar"' # Note that the single-quotes will be removed, and the
# double-quotes will be treated as part of the variable value.
somecmd $var # This runs cmd with 2 arguments: '"foo' and 'bar"'
In your case, I'm not sure where the double-quotes are coming from; they're not in the file listing you provided and the awk command won't add them. But in any case, you don't want them stored as part of the value, you want them around the variable reference:
var='foo bar' # Again, the single-quotes are removed.
somecmd "$var" # This runs cmd with a single argument: 'foo bar'
Your case is a little more complicated since you're using an array, but the same principle applies. Note that echoing a variable is highly misleading; if it shows what looks like proper quoting, that actually means there's something horribly wrong because it should show the arguments after quote removal.
So, how do you solve it? Try this:
options=()
while IFS="|" read col1 col2 || [ -n "$col1" ]; do
options+=("$col1" "$col2") # Note the double-quotes around the variable references
done <armatures
echo "options:"
printf " '$s'\n" "${options[#]}" # This prints each array element on a separate line
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 "${options[#]}" # Again, double-quotes around the reference
UPDATE: I added a test ([ -n "$col1" ]) to execute the loop for an unterminated last line in the database file.
If the double-quotes are actually in the database file, you'll have to remove them; the easiest way to handle this probably to strip quotes while adding the strings to the array, using bash's ability to so string replacement (replacing '"' with blank) while building the array:
options=()
while IFS="|" read col1 col2 || [ -n "$col1" ]; do
options+=("${col1//'"'/}" "${col2//'"'/}")
done <armatures
The main problem is with the way bash (or any other shell) splits the command lines into "words", as mentioned in the first answer. The line splitting is done on the basis of the IFS - the Internal Field Separator - which is set to <space><tab><newline> by default. The clever chaps (Stephen Bourne etc) who developed the early shells obviously thought it was a good idea for users to be able to change it. It's also nice that it can be set to a multi-character value.
So all you really need to do is set the field separator to a newline, run the awk script, then set it back. You should also quote the array when you use it.
This should work for bash:
IFS=$'\n'
options=($(awk -F"|" '{ print $1,$2 }' armatures)
IFS=$' \t\n'
The array members are now properly defined. Then:
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 "${options[#]}"
For more primitive shells like the Bourne Shell, you will probably need to set IFS by entering "IFS=" (without the quotes), opening single quotes, pressing <Enter>, then closing with a single quote. To reset it again, enter "IFS=" (without the quotes), open single quotes, enter a <space>, quote (e.g. Ctrl-V) a <tab>, hit <Enter>, then close with a single quote.