| View previous topic :: View next topic |
| Author |
Message |
grubber33 n00b

Joined: 27 Aug 2003 Posts: 50
|
Posted: Sat Aug 18, 2012 4:44 am Post subject: [SOLVED] Help creating a BASH script |
|
|
Hello, all! Despite my join date, I haven't used Linux consistently for very long and haven't yet become intimate with shell scripting. I need a script that I'm assigning to a hotkey for switching monitors/speakers and I need a bit of help. I have a general idea of how it will work and will try to write it out myself but I don't know the commands and syntax fully.
Some of the basic things I need to know how to do are isolate numbers in a line of output and declare a variable using them and tips on if/while loop syntax. The script will look something like this:
| Code: | #!/bin/sh
resolution=`xrandr|grep "current 3840"`
monitor=`xrandr|grep "DFP1 disconnected"`
if [ "$resolution" == "$null" -a "$monitor" == "$null" ]; then
audiodevice=0
elif [ "$resolution" != "$null" -a "$monitor" != "$null" ]; then
audiodevice=1
else
exit
fi
xrandr --auto
pacmd set-default-sink $audiodevice
pacmd list-sink-inputs|grep index > /tmp/audioswitch
indexcheck=`cat /tmp/audioswitch`
if [ "$indexcheck" == "$null" ]; then
exit
fi
linecount=`COMMAND_A`
currentline=1
while [ $currentline -le $linecount ]; do
input=`COMMAND_B`
pacmd move-sink-input $input $audiodevice
currentline=$[$currentline+1]
done |
So, firstly I need to fill in COMMAND_A and COMMAND_B.
COMMAND_A needs to be the number from
| Code: | [~] $ wc -l /tmp/audioswitch
2 /tmp/audioswitch |
Not sure if wc -l is what I'm looking for or if there's a command that just outputs the number. COMMAND_B needs to take the text from line $currentline of /tmp/audioswitch and output just the number. /tmp/audioswitch will look something like
| Code: | index: 3
index: 43
index: 56 |
The numbers are random.
Other than that, I'd like to know if I'm going about my if/while loops right or if there's a better way to do them.
Thanks in advance!
Last edited by grubber33 on Mon Aug 20, 2012 11:11 pm; edited 1 time in total |
|
| Back to top |
|
 |
Dominique_71 Veteran


Joined: 17 Aug 2005 Posts: 1245 Location: Switzerland (Romandie)
|
Posted: Sat Aug 18, 2012 12:41 pm Post subject: |
|
|
First, I use jack all the time, so I know nothing in pa. So, I don't know if wc -l is what you need. Is it working as you want?
If you want to read lines from a file, you can use redirections like
| Code: | while read line
do
echo "$line"
done < "some_file"
|
pipes will also work:
| Code: | ls | grep .pdf | while read i
do
echo "Converting $i to pictures using pdftoppm."
....
done
|
_________________ As the thought come before the action, revolution means revolutionary consciousness!
Fvwm-Crystal in action & here |
|
| Back to top |
|
 |
Dominique_71 Veteran


Joined: 17 Aug 2005 Posts: 1245 Location: Switzerland (Romandie)
|
Posted: Sat Aug 18, 2012 12:43 pm Post subject: |
|
|
Instead of wc -l, maybe cut can be useful in that case. Or even sed, but it will be more complicated. _________________ As the thought come before the action, revolution means revolutionary consciousness!
Fvwm-Crystal in action & here |
|
| Back to top |
|
 |
grubber33 n00b

Joined: 27 Aug 2003 Posts: 50
|
Posted: Sat Aug 18, 2012 1:44 pm Post subject: |
|
|
| Dominique_71 wrote: | | First, I use jack all the time, so I know nothing in pa. So, I don't know if wc -l is what you need. Is it working as you want? |
This might be irrelevant if I use the next part.
| Quote: | If you want to read lines from a file, you can use redirections like
| Code: | while read line
do
echo "$line"
done < "some_file"
|
|
This could work but my main issue is turning " index: 43" into "43" to be used as an integer variable.
| Quote: | pipes will also work:
| Code: | ls | grep .pdf | while read i
do
echo "Converting $i to pictures using pdftoppm."
....
done
|
|
This could be even more useful but I think I do need to use sed. I looked at the man file and it looks a bit daunting because I don't even know regular expressions yet but if someone could tell me the command to extract just the numbers from the output I'd really appreciate it. |
|
| Back to top |
|
 |
avx Advocate


Joined: 21 Jun 2004 Posts: 2063
|
Posted: Sat Aug 18, 2012 1:53 pm Post subject: |
|
|
| Quote: | | This could work but my main issue is turning " index: 43" into "43" to be used as an integer variable. |
| Code: | | awk -F: '{print $2}' < /file/containing/data | or | Code: | | app_or_cmd_to_get_value | awk -F: '{print $2}' |
that will return " 43", but in your usecase, the extra space won't matter. _________________ ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. |
|
| Back to top |
|
 |
BillWho Veteran


Joined: 03 Mar 2012 Posts: 1576 Location: US
|
Posted: Sat Aug 18, 2012 2:27 pm Post subject: |
|
|
grubber33.
Just a couple suggestions
You might want to use the check for zero length and non-zero length like so:
| Code: | resolution="1";export resolution
monitor="1";export monitor
resolution="";export resolution
monitor="";export monitor
# Check for zero length
if [[ -z "$resolution" && -z "$monitor" ]]; then echo "Yes"; else echo "No";fi
#check for non-zero length
if [[ -n "$resolution" && -n "$monitor" ]]; then echo "Yes"; else echo "No";fi
|
This check will never be non-zero length. If the file does not exist it returns cat: /tmp/audioswitch: No such file or directory so you should check for the existence of the file first.
Also try changing command substitution to $(cat /tmp/audioswitch) rather than `cat /tmp/audioswitch`
You can pipe the output of cat or anything else for that matter to while to read line by line:
| Code: | | ldd /usr/bin/eix|awk '{print $3}'|while read f;do ! [[ -z $f ]] && echo $f;done |
UPDATE:
Disregard the indexcheck=`cat /tmp/audioswitch` comment  _________________ Good luck
Since installing gentoo, my life has become one long emerge  |
|
| Back to top |
|
 |
mv Advocate


Joined: 20 Apr 2005 Posts: 3150
|
Posted: Sat Aug 18, 2012 4:04 pm Post subject: Re: Help creating a BASH script |
|
|
Better is Then you know that you have a POSIX compatible shell. If you want bash or zsh (for a beginner, I would recommand the latter, since it is likely to save you from some common mistakes), you should use bash or zsh instead of sh.
| Quote: | | Code: | if [ "$resolution" == "$null" -a "$monitor" == "$null" ]; then
audiodevice=0
elif [ "$resolution" != "$null" -a "$monitor" != "$null" ]; then
audiodevice=1
else
exit
fi |
|
You use here bash/zsh syntax, not POSIX: "==" is not POSIX. So either do not use sh (but instead bash or zsh), or make the syntax POSIX compliant.
Moreover, I would not recommend using "-a" or "-o" in tests since this might give unexpected results (e.g. if $resolution should start with "!" or "-").
What is "null" here? Do not expect that it expands to nothing if you have not initialized it, because it might be set differently in the environment! However, there are special tests for (non)emptyness. You should consult "man test".
Finally, if you format differently, you do not need the confusing, apparently unmotivated semicolons.
Here is how I would write the above:
| Code: | if [ -z "${resolution}${monitor}" ]
then audiodevice=0
elif [ -n "${resolution}" ] && [ -n "${monitor}" ]
then audiodevice=1
else exit
fi |
| Quote: | | Code: | indexcheck=`cat /tmp/audioswitch`
if [ "$indexcheck" == "$null" ]; then
exit
fi |
|
You should really consult "man test": There is a special command for testing about nonempty files:
| Code: | | test -s /tmp/audioswitch || exit 0 |
(It is my personal preference to use "test" instead of "[" when file-tests are involved. Of course you could use "[ -s /tmp/audioswitch ]" instead).
As already mentioned by other posters, there is no need to count the lines or reparse the whole file at every call: Just use read in a loop with redirection.
Then what you want to do in the last part is a trivial task and can be done e.g. with
| Code: | while read l
do pacmd move-sink-input "${l##*: }" "${audiodevice}"
done </tmp/audioswitch |
Note that you have forgotten some variable quotings in your code (for things like this, I recommend to use zsh, since variables behave there as if they are quoted).
Note also that you do not need an external command like awk or cut to modify the "l" variable: Just cutting the longest path until ": " is enough; if you use bash (even more if you use zsh) you have much more powerful possibilities here. |
|
| Back to top |
|
 |
mv Advocate


Joined: 20 Apr 2005 Posts: 3150
|
Posted: Sat Aug 18, 2012 4:29 pm Post subject: |
|
|
Oh, I almost forgot the biggest issue with the code:
Never write into world-writable directories like /tmp with a predictable filename!
(If you want to understand the reason, google about "symlink attacks".)
If you need a temporary file, use mktemp (and a "trap" to clean it up if the script is halted). In your case, you do not need a tempfile at all, since you can use a pipe:
| Code: | pacmd list-sink-inputs | grep index | while read l
do pacmd move-sink-input "${l##*: }" "${audiodevice}"
done | Note that if grep finds nothing, the "while" loop is never entered.
(BTW: In your previous code, you could also just have tested the return status of the first pipe (that with the grep command) to make sure that the file is nonempty).
Edit: If instead of "grep" you would have used "sed", you could already have formatted the line without the need to modify it later from within the shell:
| Code: | | sed -n -e 's/^.*index[^0-9]*\([0-9]\+\).*$/\1/p' |
instead of "grep index" outputs only the number after "index". (Then you can use "${l}" in the loop instead of "${l##*index: }", and it works also if the output of pacmd changes slightly, e.g. if the colon is removed or several spaces are output) |
|
| Back to top |
|
 |
Dominique_71 Veteran


Joined: 17 Aug 2005 Posts: 1245 Location: Switzerland (Romandie)
|
Posted: Sun Aug 19, 2012 12:17 pm Post subject: |
|
|
| grubber33 wrote: | | This could work but my main issue is turning " index: 43" into "43" to be used as an integer variable. |
| Code: | | cut -d " " -f 2 <"filename"> | is the simplest I can get. _________________ As the thought come before the action, revolution means revolutionary consciousness!
Fvwm-Crystal in action & here |
|
| Back to top |
|
 |
Dominique_71 Veteran


Joined: 17 Aug 2005 Posts: 1245 Location: Switzerland (Romandie)
|
Posted: Sun Aug 19, 2012 1:00 pm Post subject: |
|
|
| grubber33 wrote: | | This could be even more useful but I think I do need to use sed. I looked at the man file and it looks a bit daunting because I don't even know regular expressions yet but if someone could tell me the command to extract just the numbers from the output I'd really appreciate it. |
If the content of your file will always be something like [.*: <number>] (an arbitrary number of random characters followed by ": " and some number, the following will work:
| Code: | | cat "filename" | sed -e 's/.*: //g' |
Of course, you can substitute "cat filename" by the command that create filename.
A good introduction to sed is on the devman: sed - stream editor. You will find much more with google.
You will find a lot of basic stuffs about bash and the basic linux tools in the devman: tools reference. They are only basic stuffs, and oriented to ebuild making, but they can be useful to get started with those programs. _________________ As the thought come before the action, revolution means revolutionary consciousness!
Fvwm-Crystal in action & here |
|
| Back to top |
|
 |
khayyam Veteran


Joined: 07 Jun 2012 Posts: 1268
|
Posted: Sun Aug 19, 2012 4:59 pm Post subject: |
|
|
| Dominique_71 wrote: | | grubber33 wrote: | | This could work but my main issue is turning "index: 43" into "43" to be used as an integer variable. |
| Code: | | cut -d " " -f 2 <"filename"> |
is the simplest I can get. |
well, if the input has more than one line then we need to be more specific ...
| Code: | | INTEGER_VARIABLE=$(awk '/index:/{print $2}' input.txt) |
or, say, if we know the specific value:
| Code: | | INTEGER_VARIABLE=$(awk '/index:/{if ($2 == "43") print $2}' input.txt) |
or if we simply want the first match:
| Code: | | INTEGER_VARIABLE=$(awk '/index:/{print $2 ; exit}' input.txt) |
for 'sh' proper you would replace the command substitution "$( )" with backticks
| Dominique_71 wrote: | | Code: | | cat "filename" | sed -e 's/.*: //g' |
|
sed is a stream editor, it treats files as stdin, so no need to cat ... I know this was an example intended to replaced with stdout, but cat is so often used its worth pointing out that its wasted.
| Code: | | sed -e 's/.*: //g' input.txt |
but this will match any 'colon space' so its good to be specific, and as no editing needs to take place sed would not be my command of choice.
best ... khay |
|
| Back to top |
|
 |
wcg Guru

Joined: 06 Jan 2009 Posts: 556
|
Posted: Mon Aug 20, 2012 8:01 am Post subject: |
|
|
Not to downgrade the value of real, direct advice,
but this is invaluable to any Linux user that does not
already know bash syntax by heart:
http://shop.oreilly.com/product/9780596009656.do
(If you have doubts, find it at a library, take a look at
the tables in the back for yourself. The 3rd edition probably
contains errata fixes plus new topics.)
Advanced topics:
http://tldp.org/LDP/abs/html/
Regular expressions:
The section Syntax Bits tells what the different
flags (like RE_BACKSLASH_ESCAPE_IN_LISTS or
RE_CONTEXT_INDEP_ANCHORS and so on) mean
individually, and Predefined Syntaxes tells which
flags are defined for sed, awk, emacs, grep, egrep,
and so on.
(libpcre and perl regexes are their own subject.
More capabilities, but unless you are doing
the whole job in perl, loading perl for some simple
regex match is like taking a cruise ship out to
do some fishing, imho.) _________________ TIA |
|
| Back to top |
|
 |
grubber33 n00b

Joined: 27 Aug 2003 Posts: 50
|
Posted: Mon Aug 20, 2012 11:10 pm Post subject: |
|
|
| Thanks a lot to everyone in this thread, especially mv for all the info and wcg for the book recommendation! This will all be very helpful to me and I'm sure I can finish up the script with all the information I've got. Marking solved. |
|
| Back to top |
|
 |
wcg Guru

Joined: 06 Jan 2009 Posts: 556
|
Posted: Sat Aug 25, 2012 3:39 pm Post subject: |
|
|
Other people have said that zsh can do things that bash, tcsh, and ksh
cannot, which makes some sense. The original designer of zsh was likely
dissatisfiled with his choice of shells on the systems he was working
on, probably sh and csh, and maybe ksh. My impression is that bash
is more thoroughly documented, which helps users new to unix shell
scripts, and upstream bash maintenance does a good job of tracking
development in glibc.
(On linux, tcsh and zsh have tended to be well-maintained, too, although
I have not compiled tcsh in some years, so I do not know if its utf-8
and internationalization support is up-to-date.)
One handy bashism (this probably works for #!/bin/sh scripts, too) is placing
"set -x" at the beginning of the script and "set -" at the end of it. That creates
a sort of "verbose debug mode", where the shell will print out commands,
for loops, case statements, and so on with their expanded arguments as
it executes each statement in the script. If the script is crashing, you can
see exactly which line it is failing on, what the expanded arguments were,
and so on.
So the script starts like this:
| Code: |
#!/bin/bash
# some comment explaining the purpose of the script
# set verbose mode for diagnosing problems
set -x
# rest of script here
# turn off verbose mode
set -
# script ends
|
Once you have it working the way you want, you can comment out
the "set -x" and "set -" lines at the beginning and end of the script,
leaving them there as a reminder if you modify the script later and
need to debug it again.
(Seeing the expanded variables in command context helps debug
iptables setup scripts that do not seem to be producing the desired
effect, for example. The iptables logic may be correct, but the variables
are not expanding to the expected values when the command is executed.)
You can capture this output by running the script in a subshell:
| Code: |
(my_script.sh) 2>&1 | tee my_script.log
|
(The "2>&1" appends stderr to stdout, and "tee" is a simple command
found in /usr/bin/.) _________________ TIA |
|
| Back to top |
|
 |
mv Advocate


Joined: 20 Apr 2005 Posts: 3150
|
Posted: Sat Aug 25, 2012 7:03 pm Post subject: |
|
|
| wcg wrote: | The original designer of zsh was likely
dissatisfiled with his choice of shells on the systems he was working
on, probably sh and csh, and maybe ksh. |
I do not know much about zsh history, but I think to remember that it started just as some sort of fork of ksh.
| Quote: | | My impression is that bash is more thoroughly documented, which helps users new to unix shell scripts |
Depends on what means documented. The zsh manpages are more precise in most details than the bash manpage, for example. But if you count third-party documents/books about the shell, bash will be the clear winner. However, the zsh syntax helps to avoid most newbie-errors (which for the shell unfortunately are often security relevant so that newbies will not realize that they open possibilities for exploits).
| Quote: | | and upstream bash maintenance does a good job of tracking development in glibc. |
I do not understand what you mean here. bash and glibc are technically not related. They have in common that they are both projects paid by GNU, but that is not related to the technical side. Maybe they also both have in common that they are too blown up
| Quote: | | On linux, tcsh and zsh have tended to be well-maintained, too |
I do not know much about tcsh except that - as csh - there are several crucial things which it cannot do or cannot do without opening severe security holes; there are some documents which recommend not to use them for scripts. And since concering interactivity zsh is probably the best possible (although, unfortunately, not with its default settings), I got never interested in tcsh.
| Quote: | | I have not compiled tcsh in some years, so I do not know if its utf-8 and internationalization support is up-to-date.) |
For tcsh, I cannot tell, but for zsh it is up-to-date.
| Quote: | One handy bashism (this probably works for #!/bin/sh scripts, too) is placing
"set -x" at the beginning of the script and "set -" at the end of it. |
This is not a bashism (as you conjecture): It works in every POSIX-compatible shell (including zsh, dash, and bash). However, the command to switch off command printing is not "set -" but "set +x" (that "set -" works in bash really is a bashism).
| Quote: | You can capture this output by running the script in a subshell:
| Code: |
(my_script.sh) 2>&1 | tee my_script.log
|
|
No reason to start two iterated subshells. One is enough: | Code: | | ./my_script.sh 2>&1|tee my_script.log | (Or use | Code: | | { . ./my_script.sh; } 2>&1|tee my_script.log | if you really want to "brace" several commands. Actually, with zsh (not sure about bash) you can use |& as a shortcut for "2>&1|". Moreover, with zsh you do not need tee since you can just redirect twice:
| Code: | | my_script.sh >my_script.log |& less |
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|