Difference between pages "Watch digital TV with VDR and a streaming client" and "BaBE - Bash By Examples"

From Linuxintro
(Difference between pages)
imported>ThorstenStaerk
 
imported>ThorstenStaerk
 
Line 1: Line 1:
= Introduction =
+
<metadesc>BaBE - Bash by Examples; Your significant Linux scripting tutorial: Redirection, Piping, conditional execution, user dialogs, loops, process management, backticks, string replacement, pitfalls and much more.</metadesc>
This article describes some aspects to setup an enviroment with a VDR server and a xine stream client.
 
  
Here typically should be the obligatory screenshot of my desktop showing the running stream - I add that later on :)
+
BaBE - Bash By Examples; Your significant Linux scripting tutorial;;
  
To proceed, [[find out your distribution]] and proceed accordingly:
+
<pic src="http://www.linuxintro.org/images/Bash-scripting-mindmap.jpg" width=70% align=right caption=MindMap />
  
= SUSE Linux 12.1 =
+
= Hello world =
== The setup as basic for this article ==
+
The easiest way to get your feet wet with a programming language is to start with a program that simply outputs a trivial text, the so-called hello-world-example. Here it is for bash:
We need some Hardware - I my case:
+
* create a file named hello in your home directory with the following content:
* a laptop [http://www.staerk.de/thorsten/latitude_2100 Dell Latitude 2100]
+
[http://en.wikipedia.org/wiki/Shebang_%28Unix%29 #!/bin/bash]
* an external digital TeraTec Cinergy S2 USB with remote control
+
[[echo]] "Hello, world!"
 +
* [[open a console]], enter
 +
cd
 +
chmod +x hello
 +
* now you can execute your file like this:
 +
# ./hello
 +
Hello, world!
 +
* or like this:
 +
# bash hello
 +
Hello, world!
 +
You see - the output of your shell [[program]] is the same as if you had entered the commands into a console.
  
And of course some software repositories - in my case:
+
= calling commands =
* Base OS is openSUSE 12.1
+
In your shell script you can call every command that you can call when [[opening a console]]:
 +
<source>
 +
echo "This is a directory listing, latest modified files at the bottom:"
 +
ls -ltr
 +
echo "Now calling a browser"
 +
firefox
 +
echo "Continuing with the script"
 +
</source>
  
We setup the following componets
+
= variables =
* VDR (http://www.vdr-portal.de/)
+
== input ==
* XINE
+
To show you how to deal with variables, we will now write a script that asks for your name and greets you:
 +
echo "what is your name? "
 +
read name
 +
echo "hello $name"
 +
You see that the name is stored in a variable $name. Note the quotation marks '''"''' around "hello $name". By using these you say that you want variables to be replaced by their content. If you were to use apostrophes, the name would not be printed, but $name instead.
  
== Setup Procedure ==
+
== ${} ==
=== Installing VDR and XINE ===
+
The ${} operator stands for the variable, there is no difference if you write
 +
echo "$name"
 +
or
 +
echo "${name}"
 +
So what is the sense of this? Imagine you want to echo a string directly, without any blank, after the content of a variable:
 +
<source>
 +
echo "if I add the syllable owa to your name it will be ${name}owa"
 +
</source>
  
==== Installation channels ====
+
== common mistakes ==
* Set up installation channels
+
Note that the variable is called $name, however the correct statement to read it from the keyboard is
zypper ar http://packman.inode.at/suse/openSUSE_12.1/Essentials packman-essentials
+
  read name
zypper ar http://packman.inode.at/suse/openSUSE_12.1/Multimedia packman-multimedia
+
It is a common mistake to write
* Now the command
+
  read $name
  zypper lr -u
+
which means "read a string and store it into the variable whose name is stored in $name"
must contain
 
  | packman-essentials                | packman-essentials                | Yes    | No      | http://packman.inode.at/suse/openSUSE_12.1/Essentials                     
 
| packman-multimedia                | packman-multimedia                | Yes    | No      | http://packman.inode.at/suse/openSUSE_12.1/Multimedia
 
  
==== Installation ====
+
= parameters =
Now install the software:
+
<source>
[[yast]] -i vdr-plugin-xine
+
echo "Here are all parameters you called this script with: $@"
 +
echo "Here is parameter 1: $1"
 +
echo "Which parameter do you want to be shown? "
 +
read number
 +
args=("$@")
 +
echo "${args[$number-1]}"
 +
</source>
  
==== Configuration ====
+
= return codes =
Now [[configure]] the [[software]]. In /etc/sysconfig/vdr, replace
+
Every bash script can communicate with the rest of the system by
  VDR_PLUGINS=""
+
* sending data to [[stdout]]
by
+
* sending data to [[stderr]]
  VDR_PLUGINS="remote xine"
+
* delivering a return code
  VDR_PLUGIN_ARGS_remote="-i /dev/input/ir"
+
The return code is 0 if everything worked well. You can query it for the most recent command using $?:
  VDR_PLUGIN_ARGS_xine=""
+
<source>
and restart the video disk recorder:
+
  bootstick@bootstick:~$ echo "hello world"; echo $?
  /etc/init.d/vdr restart
+
hello world
 +
  0
 +
  bootstick@bootstick:~$ echo "hello world">/proc/cmdline; echo $?
 +
  bash: /proc/cmdline: Permission denied
 +
  1
 +
</source>
  
= SUSE Linux 11.4 =
+
In bash, true is 0 and false is any value but 0. There exist two commands, true and false that deliver true or false, respectively:
== The setup as basic for this article ==
+
bootstick@bootstick:~$ true; echo $?
We need some Hardware - I my case:
+
0
* a laptop DELL D620
+
bootstick@bootstick:~$ false; echo $?
* an external USB S2 digtital HD device TeVii S660
+
1
* a TechnoTrend remote control (because unfortunately the remote control support of TeVii S660 is buggy in my setup)
 
  
And of course some software repositories - in my case:
+
= line feeds =
* Base OS is openSUSE 11.4
+
Let's look at the following script:
* Additional package repositories: TBD
+
read name
 +
if [ $name = "Thorsten" ]; then echo "I know you"; fi
 +
Instead of a semicolon you can write a line feed like this:
 +
read name
 +
if [ $name = "Thorsten" ]
 +
  then echo "I know you"
 +
fi
 +
And instead of a line feed you can use a semicolon:
 +
read name; if [ $name = "Thorsten" ]; then echo "I know you"; fi
 +
If you want to insert a line feed where you do not need one, e.g. to make the code better readable, you must prepend it with a backslash:
 +
read \
 +
  name
 +
if [ $name = "Thorsten" ]
 +
  then \
 +
    echo "I know you"
 +
fi
  
We setup the following componets
+
= storing a command's output =
* VDR (http://www.vdr-portal.de/)
+
To read a command's output into a variable use $(), backticks or [[piping]].
* LIRC
 
* XINE
 
  
== Setup Procedure ==
+
== $() ==
=== Installing VDR and XINE ===
+
arch=$(uname -m)
 +
echo "Your system is a $arch system."
  
==== Installation channels ====
+
== backticks ==
* Set up installation channels
+
  arch=`uname -m`
  zypper ar http://packman.inode.at/suse/openSUSE_11.4/Essentials packman-essentials
+
  echo "Your system is a $arch system."
zypper ar http://packman.inode.at/suse/openSUSE_11.4/Multimedia packman-multimedia
 
* Now the command
 
  zypper lr -u
 
must contain
 
  | packman-essentials  | packman-essentials        | Yes    | Yes    |  99    | rpm-md | http://packman.inode.at/suse/openSUSE_11.4/Essentials                     
 
  | packman-multimedia  | packman-multimedia        | Yes    | Yes    |  99    | rpm-md | http://packman.inode.at/suse/openSUSE_11.4/Multimedia                     
 
  
* libxine2 removed; xine-ui from other vendor'''. Install VDR and xine packages - sou might need also some dependent packages so use zypper to resolve dependencies
+
== piping ==
** Remark: '''2012-07-18: Updated List'''
+
[[Piping]] is a very elegant concept in the Linux world. It allows you to take one command's output and use it as input for the next command. Now you can divide tasks into smaller tasks. For example instead of having a program counting all files in a directory you use one program (ls) to ''list'' all files in a directory and one program (wc) to count the lines:
** removed package libxine2
+
<source>
** xine-ui from openSUSE '''not''' packman.links2linux.de
+
ls | wc -l
** downgraded vdr-plugin-xine to version 0.9.3
+
</source>
** To query the packages in this format:
+
You can also put the output into a variable, in this case $arch:
  printf " %-22s | %-22s | %-30s | %s\
+
<source>
" "Name" "Version" "Vendor" "Project URL"
+
  uname -m | while read arch; do echo "Your system is a $arch system."; done
printf " -----------------------+------------------------+--------------------------------+-------------------------------\
+
</source>
"
+
 
  rpm -qa --queryformat ' %-22{name} | %-22{version} | %-30{vendor} | %{url} \
+
== comparison ==
' | egrep '(vdr|xine)'
+
The advantage of using backticks over $() is that backticks also work in the sh shell. The advantage of using $() over backticks is that you can cascade them. In the example below we use this possibility to get a list of all files installed with rpm on the system:
+
  filelist=$([[rpm]] -ql $(rpm -qa))
Name                  | Version                | Vendor                        | Project URL
+
You can use the piping approach if you need to cascade in sh, but this is not focus of this bash tutorial.
-----------------------+------------------------+--------------------------------+-------------------------------
+
 
  libxine1-pulse        | 1.1.20.1              | http://packman.links2linux.de  | http://www.xine-project.org/home
+
== common mistakes ==
  xine-ui                | 0.99.5                | openSUSE                      | http://xine.sourceforge.net
+
Usually unexperienced programmers try something like
  libxine1-codecs        | 1.1.20.1              | http://packman.links2linux.de | http://www.xine-project.org/home
+
  uname -m | read arch
  vdr-plugin-streamdev  | 0.0_CVS20080716080048 | openSUSE                      | http://streamdev.vdr-developer.org/
+
which [http://mywiki.wooledge.org/BashFAQ/024 does not work]. You must embed the read into a while loop.
  libdvdread3            | 0.9.7                  | http://packman.links2linux.de  | http://www.dtek.chalmers.se/groups/dvd/index.shtml
+
 
  libxine1              | 1.1.20.1               | http://packman.links2linux.de | http://www.xine-project.org/home
+
= conditions =
  vdr                    | 1.6.0                  | openSUSE                      | http://www.tvdr.de/  
+
The easiest form of a condition in bash is this '''if''' example:
xine-skins            | 1.0.3                  | Packman                        | http://xinehq.de/index.php/skins
+
echo "what is your name? "
  vdr-plugin-epgsearch  | 0.9.24                | obs://build.opensuse.org/vdr  | http://winni.vdr-developer.org/epgsearch/downloads/vdr-epgsearch-0.9.24.tgz
+
read name
kdebase4-runtime-xine  | 4.6.0                  | openSUSE                      | http://www.kde.org/
+
  if [ $name = "Thorsten" ]; then echo "I know you"; fi
  libdvdread4            | 4.1.3                  | openSUSE                      | http://www.mplayerhq.hu/
+
Now let's look closer at this, why does it work? Why is there a blank needed behind the [ sign? The answer is that [ is just an ordinary [[command]] in the shell. It delivers a return code for the expression that follows till the ] sign. To prove this we can write a script:
  xinetd                | 2.3.14                | openSUSE                      | http://www.xinetd.org/
+
<source>
phonon-backend-xine    | 4.4.4                  | openSUSE                      | http://phonon.kde.org/
+
if true; then echo "the command following if true is being executed"; fi
  vdr-plugin-cutalot    | 0.0.3                  | openSUSE                      | http://www.vdr-wiki.de/wiki/index.php/Cutalot-plugin
+
if false; then echo "this will not be shown"; fi
libxine1-gnome-vfs    | 1.1.20.1              | http://packman.links2linux.de  | http://www.xine-project.org/home
+
</source>
  vdr-plugin-xine        | 0.9.3                 | openSUSE                      | http://home.vr-web.de/~rnissl/
+
 
 +
== empty strings ==
 +
An empty string evaluates to false inside the [ ] operators so it is possible to check if a string ''result'' is empty like this:
 +
# result=
 +
# if [ $result ]; then echo success; fi
 +
# result=good
 +
  # if [ $result ]; then echo success; fi
 +
  success
 +
 
 +
== arithmetic expressions ==
 +
You can compare integer numbers like this:
 +
  echo "what is your age? "
 +
  read age
 +
  if (( $age >= 21 )); then echo "Let's talk about sex."; fi
 +
However bash does not understand floating point numbers. To compare floating numbers you will use external programs such as bc:
 +
<source>
 +
  $ if [ $(echo "2.1<2.2"|bc) = 1 ]; then echo "correct"; else echo "wrong"; fi
 +
  correct
 +
  $ if [ $(echo "2.1>2.2"|bc) = 1 ]; then echo "correct"; else echo "wrong"; fi
 +
wrong
 +
</source>
 +
 
 +
== not equal ==
 +
To check if a variable is NOT equal to whatever, use !=:
 +
<source>
 +
  if [ "$LANG" != "C" ]; then echo "please set your system lanugage to C"; fi
 +
</source>
 +
 
 +
== common mistakes ==
 +
Common mistakes are:
 +
* to forget the blank behind/before the [ or ] character
 +
* to forget the blank behind/before the equal sign
 +
* see [[what does "unary operator expected" mean]]
 +
 
 +
= Redirections =
 +
To redirect the output of a [[command]] to a file you have to consider that there are two output streams in UNIX, [[stdout,stderr and stdin|stdout and stderr]].
 +
 
 +
= filling files =
 +
To create a file, probably the easiest way is to use [[cat]]. The following example writes text into README till a line occurs that only contains the string "EOF":
 +
  cat >README<<EOF
 +
This is line 1
 +
  This is line 2
 +
This is the last line
 +
EOF
 +
Afterwards, README will contain the 3 lines below the cat command and above the line with EOF.
 +
 
 +
= loops =
 +
 
 +
== for loops ==
 +
Here is an example for a for-loop. It makes a [[backup]] of all text files:
 +
  for i in *.txt; do [[cp]] $i $i.bak; done
 +
The above command takes each .txt file in the current directory, stores it in the [[variable]] $i and copies it to $i.bak. So ''file''.txt gets copied to ''file''.txt.bat.
 +
 
 +
You can also use subsequent numbers as a for loop using the command seq like this:
 +
  for i in $([[seq]] 1 1 3); do [[echo]] $i; done
  
* Install optional VDR packages
+
== while loops ==
  vdr-plugin-cutalot 0.0.3 openSUSE http://www.vdr-wiki.de/wiki/index.php/Cutalot-plugin
+
  $ while true; do read line; done
vdr-plugin-epgsearch 0.9.24 obs://build.opensuse.org/vdr http://winni.vdr-developer.org/epgsearch/downloads/vdr-epgsearch-0.9.24.tgz
 
vdr-plugin-streamdev 0.0_CVS20080716080048 openSUSE http://streamdev.vdr-developer.org/
 
  
'''Remark:''' Using the xine plugin (from repository PackMan) you could use xine for a complete VDR client (with full control over all menus).
+
= negations =
 +
You can negate a result with the ! operator. $? is the last command's return code:
 +
# true
 +
# echo $?
 +
0
 +
# false
 +
# echo $?
 +
1
 +
# ! true
 +
# echo $?
 +
1
 +
# ! false
 +
# echo $?
 +
0
 +
So you get an endless loop out of:
 +
while ! false; do echo hallo; done
  
* Adapt plugin configuration /etc/sysconfig/vdr (the plugin "remote" is optional and can be dropped, if you use lirc)
+
The following code checks the file /tmp/success to contain "success". As long as this is ''not'' the case it continues checking:
#
+
  while ! (grep "success" /tmp/success)
# With plugin remote plugin:
+
  do
  #
+
  sleep 30
VDR_PLUGINS="remote xine"
+
  done
VDR_PLUGIN_ARGS_remote="-i /dev/input/ir"
 
VDR_PLUGIN_ARGS_xine=""
 
  #
 
# Without plugin remote plugin:
 
  #
 
VDR_PLUGINS="remote xine"
 
VDR_PLUGIN_ARGS_remote="-i /dev/input/ir"
 
VDR_PLUGIN_ARGS_xine=""
 
* Restart VDR
 
  /etc/init.d/vdr restart
 
* start watching VDR: - You should get a OSD message from VDR that tells you to train VDR to understand your remote control or keyboard sequences
 
xine vdr://
 
  
In this setup we proceed to integrate the Technotrend Remote control to be able to send signals to LIRCD and than configure VDR to understand LIRCD commands...
+
The following code checks if the file dblog.log exists. As long as this is not the case it tries to download it via ftp:
 +
<source>
 +
while ! (test -e dblog.log); do
 +
  ftp -p ftp://user:password@server/tmp/dblog.log >/dev/null
 +
  echo -en "."
 +
  sleep 1
 +
done
 +
</source>
  
=== TechnoTrend USB Remote-Control ===
+
== common mistakes ==
==== Low Level Test with mode2 ====
+
* bash is very picky regarding spaces. There MUST be a space after the ! if it means negation.
To test, if the IR keys are recognized on a very low level test using command mode2. This works also without lircd.
 
furka:~ # mode2 -d /dev/lirc0
 
space 3508766
 
pulse 806
 
space 744
 
pulse 806
 
  
==== lircd configuration =====
+
= sending a process to the background =
In openSUSE 11.4 lircd is configured via
+
To send a process to the [[background]], use the ampersand sign (&):
===== /etc/sysconfig/lirc =====
+
  firefox & echo "Firefox has been started"
## Description:    lirc (infrared remote control) configuration
+
You see a newline is not needed after the &
## Type:          string
 
## Default:        "660"
 
## ServiceRestart: lirc
 
#
 
# permissions for /dev/lircd
 
#
 
LIRCD_DEV_PERMISSIONS="660"
 
 
## Type:        string
 
## Default:    "root:video"
 
#
 
# owner and group for /dev/lircd
 
#
 
LIRCD_DEV_OWNER="root:video"
 
 
## Type:        string
 
## Default:    ""
 
#
 
# use given driver
 
#
 
LIRCD_DRIVER=""
 
 
## Type:        string
 
## Default:    ""
 
#
 
# read from given device
 
#
 
LIRCD_DEVICE="/dev/lirc0"
 
 
## Type:        string(lirc_bt829,lirc_gpio,lirc_i2c,lirc_it87,lirc_parallel,lirc_sir,ir-kbd-i2c,ir-kbd-gpio)
 
## Default:    ""
 
#
 
# load given lirc driver module
 
#
 
LIRC_MODULE="lirc_ttusbir"
 
 
## Type:       string
 
  ## Default:    ""
 
#
 
# listen for network connections on specified port.
 
# WARNING: don't use this on a machine with an internet
 
# connection as lircd is running as root!
 
#
 
LIRCD_LISTENPORT=
 
 
## Type:        string
 
## Default:    ""
 
#
 
# connect lircd to specified host
 
#
 
LIRCD_CONNECT=
 
===== /etc/lirc/lircd.conf =====
 
/etc/lirc/lircd.conf (enthaelt die definition der Fernbedienungstasten)
 
#
 
# this config file was automatically generated
 
# using lirc-0.8.3(default) on Wed Jul  2 21:51:15 2008
 
#
 
# contributed by
 
#
 
# brand:                                  Technotrend
 
# model no. of remote control:
 
# devices being controlled by this remote: TV-Card
 
#
 
 
 
begin remote
 
 
 
  name      Technotrend
 
  bits                6
 
  flags RC5|CONST_LENGTH
 
  eps                30
 
  aeps                100
 
 
 
  one                889  889
 
  zero                889  889
 
  plead              889
 
  pre_data_bits        7
 
  pre_data          0x55
 
  gap              113792
 
  toggle_bit            2
 
  frequency        36000
 
  duty_cycle          50
 
 
 
      begin codes
 
          Power                    0x01
 
          Mute                    0x18
 
          1                        0x03
 
          2                        0x04
 
          3                        0x05
 
          4                        0x06
 
          5                        0x07
 
          6                        0x08
 
          7                        0x09
 
          8                        0x0A
 
          9                        0x0B
 
          0                        0x0C
 
          Audio                    0x1A
 
          Repeat                  0x02
 
          vol-                    0x26
 
          vol+                    0x25
 
          Text                    0x19
 
          ch-                      0x24
 
          ch+                      0x23
 
          Exit                    0x13
 
          OK                      0x0F
 
          Up                      0x0D
 
          Down                    0x11
 
          Left                    0x0E
 
          Right                    0x10
 
          Red                      0x14
 
          Green                    0x15
 
          Yellow                  0x16
 
          Blue                    0x17
 
          Record                  0x3A
 
          Play                    0x3B
 
          Stop                    0x3C
 
          Info                    0x12
 
          Rew                      0x3D
 
          Pause                    0x3E
 
          Fwd                      0x3F
 
          EPG                      0x22
 
      end codes
 
 
end remote
 
  
==== lircd start and start at boot time ====
+
= forking a process =
furka:~ # rclirc start
+
You can build a process chain using parantheses. This is useful if you want to have two instruction streams being executed in parallel:
  Starting lircd (/dev/lirc0)                                         done
+
<source>
furka:~ # chkconfig lirc on
+
  (find -iname "helloworld.txt") & (sleep 5; echo "Timeout exceeded, killing process"; kill $!)
 +
</source>
  
==== lircd-client Connection Test ====
+
= functions =
To test, if a client of lircd could recognize key presses use command irw. This command is part of package lirc.
+
To define a function in bash, use a non-keyword and append opening and closing parentheses. Here a function greet is defined and it prints "Hello, world!". Then it is called:
  furka:~ # irw
+
  # greet()
  000000000000154c 00 0 Technotrend
+
  {
000000000000154c 01 0 Technotrend
+
    echo "Hello, world"
  000000000000154c 00 0 Technotrend
+
  }
  000000000000154c 01 0 Technotrend
+
  # greet
  
==== Trouble-Shooting ====
+
If you hand over parameters you can greet any planet you like:
* If you do NOT have a valid config file for your remote you might create one interactive using irrecord
+
  # greet()
  irrecord -d /dev/lirc0 file
+
{
* Remarks
+
    echo "Hello, $1"
** "file" is the output file
+
}
** You should prefer to fetch a config file from http://www.lirc.org/remotes/
+
# greet Mars
** I successfully tried http://lirc.sourceforge.net/remotes/technotrend/TV-Card
+
Hello, Mars
 +
# greet World
 +
Hello, World
  
=== lirc to talk with vdr ===
+
= react on CTRL_C =
* VDR automatically tries to get signals/commands from LIRC, but you need to help VDR to understand whoat to do, if a special LIRC command is received. You could either "train" VDR and press keys in the training mode, or provide a ready-to-go definition file. File '''/etc/vdr/remote.conf''' needs to contain those additional entries for accepting lircd keys:
+
The command trap allows you to trap CTRL_C keystrokes so your script will not be aborted
LIRC.Up        Up
+
<source>
LIRC.Down      Down
+
#!/bin/bash
LIRC.Menu      Repeat
 
LIRC.Ok        OK
 
LIRC.Back      Exit
 
LIRC.Left      Left
 
LIRC.Right      Right
 
LIRC.Red        Red
 
LIRC.Green      Green
 
LIRC.Yellow    Yellow
 
LIRC.Blue      Blue
 
LIRC.0          0
 
LIRC.1          1
 
LIRC.2          2
 
LIRC.3          3
 
LIRC.4          4
 
LIRC.5          5
 
LIRC.6          6
 
LIRC.7          7
 
LIRC.8          8
 
LIRC.9          9
 
LIRC.Info      Info
 
LIRC.Play      Play
 
LIRC.Pause      Pause
 
LIRC.Stop      Stop
 
LIRC.Record    Record
 
LIRC.FastFwd    Fwd
 
LIRC.FastRew    Rew
 
LIRC.Power      Power
 
LIRC.Channel+  ch+
 
LIRC.Channel-  ch-
 
LIRC.Volume+    vol+
 
LIRC.Volume-    vol-
 
LIRC.Mute      Mute
 
  
* Restart VDR again to activate the new configuration
+
trap shelltrap INT
  
== UNSORTED STUFF ==
+
shelltrap()
Auf dem Laptop Latitude D820 habe ich einen vdr installiert mit einer externen USB-Box TVii 660
+
{
=== Installation of the DVB-S2 USB-Box ===
+
    echo "You pressed CTRL_C, but I don't let you escape"
<TBD>
+
}
=== Plugins, plugins, plugins ... ===
 
Why are there so much differetn plugins for VDR to provide video streams? They all have different feature sets and use cases. While in the use case to watch TV on a Laptop where the TV-Card/Box is connected is matching to the plugine xine, there are other usescases for network streaming.
 
  
TBD We need / explain the following VDR plugins:
+
while true; do read line; done
* xine
+
</source>
* streamdev
 
* libxineoutput
 
  
=== Video clients ===
+
;Note: You can still ''pause'' your script by pressing CTRL_Z, send it to the [[background]] and kill it there. To catch CTRL_Z, replace INT by TSTP in the above example. To get an overview of all signals that you might be able to trap, [[open a console]] and enter
==== Remote Streaming: vdr-plugin-streamdev ====
+
  kill -l
vlc - What is already running is that you can watch TV and also switch the programm. But you do not have full control over VDR using that method.
 
vlc http://127.0.0.1:3000/PES/1
 
URL in general:
 
http://<hostip>:<port>/[PES|xx]/<prognummer>
 
You need to setup the file /etc/vdr/streamdev.conf:
 
  # streamdev.conf:
 
<TBD>
 
  
Passender /etc/sysconfig/vdr Auszug
+
= helpful programs =
  VDR_PLUGINS="streamdev"
+
== awk: read a specific column ==
 +
[[awk]] is a program that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can extract columns from a text. Let's imagine you want to use the [[program]] [[vmstat]] to find out how high the CPU user load was. Here is the output from vmstat:
  
==== vdr-plugin-xineliboutput ====
+
<pic src="http://www.linuxintro.org/images/vmstat.png" align=text width=100% caption=VmStat />
With xineliboutput
 
vlc tcp://localhost:37890
 
  
But how to switch the program using this connection?
+
We see the user load is in colum 13, and we only want to print this column. We do it with the following command:
 +
vmstat 5 | awk '{print $13}'
 +
This will print a line from vmstat all 5 seconds and only write the column 13. It looks like this:
 +
# vmstat 5 | awk '{print $13}'
 +
 +
us
 +
1
 +
1
 +
0
 +
1
 +
To store the CPU user load into a variable we use
 +
load=$(vmstat 1 2 | tail -n 1 | awk '{print $13}')
 +
What happens here? First vmstat outputs some data in its first line. The data about CPU load can only be rubbish because it did not have any time to measure it. So we let it output 2 lines and wait 1 second between them ( => vmstat 1 2 ). From this command we only read the last line ( => tail -n 1 ). From this line we only print column 13 ( => awk '{print $13}' ). This output is stored into the variable $load ( => load=$(...) ).
  
==== Best practice local access with <html><acronym title="On Screen Display">OSD</acronym></html>:
+
== grep: search a string ==
vdr-plugin-xine ====
+
[[grep]] is a [[program]] that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can extract lines that contain a string or match a [[regex]] pattern. Let's imagine you want all external links from www.linuxintro.org's main page:
'''This is the recommended way if you want to watch TV on the computer where VDR is running.'''
+
wget -O linuxintro.txt http://www.linuxintro.org
 +
grep "http:" linuxintro.txt
  
Using the xine plugin (from repository PackMan) you could use xine for a complete VDR client (with full control over all menus).
+
== sed: replace a string ==
 +
[[sed]] is a [[program]] that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can replace a string by another one. Let's imagine you want to print your distribution's name, but lsd_release outputs too much:
 +
# lsb_release -d
 +
Description:    openSUSE 12.1 (x86_64)
 +
You want to remove this string "Description" so you replace it by nothing:
 +
lsb_release -d | sed "s/Description:\t//"
 +
openSUSE 12.1 (x86_64)
  
* install it:  
+
Once you understand [[regular expressions]] you can use sed with them:
yast -i vdr-plugin-xine
 
  
* Matching line in /etc/sysconfig/vdr (the plugin "remote" is optional and can be dropped, if you use lirc)
+
* to replace protocol names for a given port (in this case 3200) in /etc/services:
  VDR_PLUGINS="remote xine"
+
sed -ri "s/.{16}3200/sapdp00 3200/" /etc/services
  VDR_PLUGIN_ARGS_remote="-i /dev/input/ir"
+
* if you have an [[apache]] [[web server]] here's how you get the latest websites that have been requested:
  VDR_PLUGIN_ARGS_xine=""
+
<source>
 +
cat /var/log/apache2/access_log | sed ";.*\(GET [^\"]*\).*;\1;"
 +
</source>
  
* Restart VDR
+
== tr: replace linebreaks ==
  /etc/init.d/vdr restart
+
[[sed]] is a [[program]] that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can replace a character by another one, even over line breaks.
 +
For example here is how you remove all empty lines from your processor information:
 +
<source>
 +
# cat /proc/cpuinfo | while read a; do ar=$(echo -n $a|tr '\n' ';')
 +
if [ "$ar" <> ";" ]; then echo "$ar"; fi; done
 +
</source>
  
* start watching TV:
+
== wc: count ==
  xine vdr://
+
With the command wc you can count words, characters and lines. wc -l counts lines. For example to find out how many semicolons are in a line, use the following statement:
 +
  while read line
 +
do echo "$line" | tr '\n' ' ' | sed "s/;/\n/g" | wc -l
 +
done
 +
It lets you input a line of text, counts the semicolons in it and outputs the number.
  
=== Remote Control ===
+
How does it do this?
TBD: translate de->en
 
==== TeVii S660 remote control is a bit painfull for me ====
 
Translation: <TBD>
 
* Mit dem Update von c.a. Mitte März auf den Herstellerseiten (auf Simplon Download/Tevii) geht nun die Erkennung der Fernbedienung
 
* Kleiner Patch in ./v4l/dw2102.c die Ausgaben von "query RC..." auskommentiert (also nicht aktiv), weil sonst der syslog voll läuft
 
* VDR meldet (ohne Config s.u.) beim Starten, dass er kein lirc findet, allerdings scheint die Fernbedienung als Tastatur zur funktionieren:
 
** Test mit fb bringt messages im syslog (kann ggf später entfallen)
 
** xev zeigt tastaturreaktion, wenn man fb bedient
 
** KDE lautstärke regler funktioniert bereits :)
 
** Cursorbewegungen (auf/ab/rechts/links) funktinieren u.a. hier im wiki :)
 
** war jetzt nach einigen Eingaben nicht stabil - prüfen!
 
  
In den VDR bindet  man die Fernbedienung mit dem plugin remote ein.
+
It reads lines from your keyboard (while read line). It outputs the line (echo "$line"), but it does not output it in the console. The pipe (|) redirects the output to the input stream of the command tr. The command tr replaces the ENTER ('\
Passender Auszug aus der /etc/sysconfig/vdr
+
') by a space (' '). The pipe (|) redirects the output to the input stream of sed. sed substitutes ("s/) the semicolon (;) by (/) a linefeed (\
  VDR_PLUGINS="remote"
+
), globally (/g"). The pipe redirects the output to the input stream of the wc -l command that outputs the count of lines.
  VDR_PLUGIN_ARGS_remote="-i /dev/input/ir
 
  
Die Berechtigungen für das IR-device (/dev/input/ir) müssen so eingestellt werden,
+
== dialog: create dialogs ==
dass der VDR (Benutzer vdr) darauf Zugreifen darf.
+
Dialog is a command that helps you creating dialogs in the shell. The answers given by the user are send to [[stderr]] and/or influence the command's return code. For example if you run this script:
  chown vdr:users /dev/input/ir
+
#!/bin/bash
 +
if (dialog --title "Message"  --yesno "Are you having fun?" 6 25)
 +
then echo "glad you have fun"
 +
else echo "sad you don't have fun"
 +
fi
 +
It will display this dialog:
  
Die FB muss beim ersten Benutzen im VDR trainiert werden, wenn die Datei /etc/vdr/remote.conf nicht
+
[[File:snapshot-dialog.png]]
angepasst wird (Datei stelle ich bei Bedarf zur Verfügung :).
 
  
=== openSUSE 11.2 package list sorted  by "vendor"  ===
+
This has been taken from http://www.linuxjournal.com/article/2807. Read there for more info.
rpm -qa --queryformat "%{NAME} %{VENDOR}\
 
" | egrep '(vdr|xine)' | sort -k2
 
* Packman
 
gxine Packman
 
gxine-browser-plugin Packman
 
xine-browser-plugin Packman
 
xine-skins Packman
 
* http://packman.links2linux.de
 
libdvdread3 http://packman.links2linux.de
 
libdvdread4 http://packman.links2linux.de
 
libxine1 http://packman.links2linux.de
 
libxine1-codecs http://packman.links2linux.de
 
libxine1-gnome-vfs http://packman.links2linux.de
 
libxine1-pulse http://packman.links2linux.de
 
vdr-plugin-xine http://packman.links2linux.de
 
xine-ui http://packman.links2linux.de
 
* obs://build.opensuse.org/vdr
 
vdr-plugin-xineliboutput obs://build.opensuse.org/vdr
 
* openSUSE
 
phonon-backend-xine openSUSE
 
vdr openSUSE
 
vdr-plugin-cutalot openSUSE
 
vdr-plugin-epgsearch openSUSE
 
vdr-plugin-remote openSUSE
 
vdr-plugin-streamdev openSUSE
 
xinetd openSUSE
 
  
 
= See also =
 
= See also =
* [[watch TV]]
+
* [[bash]]
* http://packman.links2linux.de/package/vdr-plugin-xine/229857
+
* [[shell]]
 +
* [[scripting]]
 +
* [[bash operators]]
 +
* http://en.wikibooks.org/wiki/Bash_Shell_Scripting
 +
* http://ryanstutorials.net/linuxtutorial/scripting.php
 +
* http://wiki.linuxquestions.org/wiki/Bash_tips
 +
* http://wiki.linuxquestions.org/wiki/Bash
 +
* http://linuxconfig.org/Bash_scripting_Tutorial
 +
* http://steve-parker.org/sh/intro.shtml
 +
* http://mywiki.wooledge.org/BashFAQ
 +
* http://mywiki.wooledge.org/BashPitfalls
 +
 
 +
[http://www.facebook.com/sharer.php?u=http%3A%2F%2Fwww.linuxintro.org%2Fwiki%2FShell_scripting_tutorial&t=Shell%20scripting%20Tutorial&src=sp Share on Facebook]
 +
 
 +
<stumbleuponbutton />
 +
 
 +
[[Category:Mindmap]]
 +
[[Category:Learning]]

Revision as of 08:06, 30 March 2020


BaBE - Bash By Examples; Your significant Linux scripting tutorial;;

MindMap

Hello world

The easiest way to get your feet wet with a programming language is to start with a program that simply outputs a trivial text, the so-called hello-world-example. Here it is for bash:

  • create a file named hello in your home directory with the following content:
#!/bin/bash
echo "Hello, world!"
cd
chmod +x hello
  • now you can execute your file like this:
# ./hello
Hello, world!
  • or like this:
# bash hello
Hello, world!

You see - the output of your shell program is the same as if you had entered the commands into a console.

calling commands

In your shell script you can call every command that you can call when opening a console: <source>

echo "This is a directory listing, latest modified files at the bottom:"
ls -ltr
echo "Now calling a browser"
firefox
echo "Continuing with the script"

</source>

variables

input

To show you how to deal with variables, we will now write a script that asks for your name and greets you:

echo "what is your name? "
read name
echo "hello $name"

You see that the name is stored in a variable $name. Note the quotation marks " around "hello $name". By using these you say that you want variables to be replaced by their content. If you were to use apostrophes, the name would not be printed, but $name instead.

${}

The ${} operator stands for the variable, there is no difference if you write

echo "$name"

or

echo "${name}"

So what is the sense of this? Imagine you want to echo a string directly, without any blank, after the content of a variable: <source>

echo "if I add the syllable owa to your name it will be ${name}owa"

</source>

common mistakes

Note that the variable is called $name, however the correct statement to read it from the keyboard is

read name

It is a common mistake to write

read $name

which means "read a string and store it into the variable whose name is stored in $name"

parameters

<source> echo "Here are all parameters you called this script with: $@" echo "Here is parameter 1: $1" echo "Which parameter do you want to be shown? " read number args=("$@") echo "${args[$number-1]}" </source>

return codes

Every bash script can communicate with the rest of the system by

  • sending data to stdout
  • sending data to stderr
  • delivering a return code

The return code is 0 if everything worked well. You can query it for the most recent command using $?: <source>

bootstick@bootstick:~$ echo "hello world"; echo $?
hello world
0
bootstick@bootstick:~$ echo "hello world">/proc/cmdline; echo $?
bash: /proc/cmdline: Permission denied
1

</source>

In bash, true is 0 and false is any value but 0. There exist two commands, true and false that deliver true or false, respectively:

bootstick@bootstick:~$ true; echo $?
0
bootstick@bootstick:~$ false; echo $?
1

line feeds

Let's look at the following script:

read name
if [ $name = "Thorsten" ]; then echo "I know you"; fi

Instead of a semicolon you can write a line feed like this:

read name
if [ $name = "Thorsten" ]
  then echo "I know you"
fi

And instead of a line feed you can use a semicolon:

read name; if [ $name = "Thorsten" ]; then echo "I know you"; fi

If you want to insert a line feed where you do not need one, e.g. to make the code better readable, you must prepend it with a backslash:

read \
  name
if [ $name = "Thorsten" ]
  then \
    echo "I know you"
fi

storing a command's output

To read a command's output into a variable use $(), backticks or piping.

$()

arch=$(uname -m)
echo "Your system is a $arch system."

backticks

arch=`uname -m`
echo "Your system is a $arch system."

piping

Piping is a very elegant concept in the Linux world. It allows you to take one command's output and use it as input for the next command. Now you can divide tasks into smaller tasks. For example instead of having a program counting all files in a directory you use one program (ls) to list all files in a directory and one program (wc) to count the lines: <source>

ls | wc -l

</source> You can also put the output into a variable, in this case $arch: <source>

uname -m | while read arch; do echo "Your system is a $arch system."; done

</source>

comparison

The advantage of using backticks over $() is that backticks also work in the sh shell. The advantage of using $() over backticks is that you can cascade them. In the example below we use this possibility to get a list of all files installed with rpm on the system:

filelist=$(rpm -ql $(rpm -qa))

You can use the piping approach if you need to cascade in sh, but this is not focus of this bash tutorial.

common mistakes

Usually unexperienced programmers try something like

uname -m | read arch

which does not work. You must embed the read into a while loop.

conditions

The easiest form of a condition in bash is this if example:

echo "what is your name? "
read name
if [ $name = "Thorsten" ]; then echo "I know you"; fi

Now let's look closer at this, why does it work? Why is there a blank needed behind the [ sign? The answer is that [ is just an ordinary command in the shell. It delivers a return code for the expression that follows till the ] sign. To prove this we can write a script: <source>

if true; then echo "the command following if true is being executed"; fi
if false; then echo "this will not be shown"; fi

</source>

empty strings

An empty string evaluates to false inside the [ ] operators so it is possible to check if a string result is empty like this:

# result=
# if [ $result ]; then echo success; fi
# result=good
# if [ $result ]; then echo success; fi
success

arithmetic expressions

You can compare integer numbers like this:

echo "what is your age? "
read age
if (( $age >= 21 )); then echo "Let's talk about sex."; fi

However bash does not understand floating point numbers. To compare floating numbers you will use external programs such as bc: <source>

$ if [ $(echo "2.1<2.2"|bc) = 1 ]; then echo "correct"; else echo "wrong"; fi
correct
$ if [ $(echo "2.1>2.2"|bc) = 1 ]; then echo "correct"; else echo "wrong"; fi
wrong

</source>

not equal

To check if a variable is NOT equal to whatever, use !=: <source>

if [ "$LANG" != "C" ]; then echo "please set your system lanugage to C"; fi

</source>

common mistakes

Common mistakes are:

Redirections

To redirect the output of a command to a file you have to consider that there are two output streams in UNIX, stdout and stderr.

filling files

To create a file, probably the easiest way is to use cat. The following example writes text into README till a line occurs that only contains the string "EOF":

cat >README<<EOF
This is line 1
This is line 2
This is the last line
EOF

Afterwards, README will contain the 3 lines below the cat command and above the line with EOF.

loops

for loops

Here is an example for a for-loop. It makes a backup of all text files:

for i in *.txt; do cp $i $i.bak; done

The above command takes each .txt file in the current directory, stores it in the variable $i and copies it to $i.bak. So file.txt gets copied to file.txt.bat.

You can also use subsequent numbers as a for loop using the command seq like this:

for i in $(seq 1 1 3); do echo $i; done

while loops

$ while true; do read line; done

negations

You can negate a result with the ! operator. $? is the last command's return code:

# true
# echo $?
0
# false
# echo $?
1
# ! true
# echo $?
1
# ! false
# echo $?
0

So you get an endless loop out of:

while ! false; do echo hallo; done

The following code checks the file /tmp/success to contain "success". As long as this is not the case it continues checking:

while ! (grep "success" /tmp/success)
do
  sleep 30
done

The following code checks if the file dblog.log exists. As long as this is not the case it tries to download it via ftp: <source>

while ! (test -e dblog.log); do
  ftp -p ftp://user:password@server/tmp/dblog.log >/dev/null
  echo -en "."
  sleep 1
done

</source>

common mistakes

  • bash is very picky regarding spaces. There MUST be a space after the ! if it means negation.

sending a process to the background

To send a process to the background, use the ampersand sign (&):

firefox & echo "Firefox has been started"

You see a newline is not needed after the &

forking a process

You can build a process chain using parantheses. This is useful if you want to have two instruction streams being executed in parallel: <source>

(find -iname "helloworld.txt") & (sleep 5; echo "Timeout exceeded, killing process"; kill $!)

</source>

functions

To define a function in bash, use a non-keyword and append opening and closing parentheses. Here a function greet is defined and it prints "Hello, world!". Then it is called:

# greet()
{
    echo "Hello, world"
}
# greet

If you hand over parameters you can greet any planet you like:

# greet()
{
    echo "Hello, $1"
}
# greet Mars
Hello, Mars
# greet World
Hello, World

react on CTRL_C

The command trap allows you to trap CTRL_C keystrokes so your script will not be aborted <source>

  1. !/bin/bash

trap shelltrap INT

shelltrap() {

   echo "You pressed CTRL_C, but I don't let you escape"

}

while true; do read line; done </source>

Note
You can still pause your script by pressing CTRL_Z, send it to the background and kill it there. To catch CTRL_Z, replace INT by TSTP in the above example. To get an overview of all signals that you might be able to trap, open a console and enter
kill -l

helpful programs

awk: read a specific column

awk is a program that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can extract columns from a text. Let's imagine you want to use the program vmstat to find out how high the CPU user load was. Here is the output from vmstat:

VmStat

We see the user load is in colum 13, and we only want to print this column. We do it with the following command:

vmstat 5 | awk '{print $13}'

This will print a line from vmstat all 5 seconds and only write the column 13. It looks like this:

# vmstat 5 | awk '{print $13}'

us
1
1
0
1

To store the CPU user load into a variable we use

load=$(vmstat 1 2 | tail -n 1 | awk '{print $13}')

What happens here? First vmstat outputs some data in its first line. The data about CPU load can only be rubbish because it did not have any time to measure it. So we let it output 2 lines and wait 1 second between them ( => vmstat 1 2 ). From this command we only read the last line ( => tail -n 1 ). From this line we only print column 13 ( => awk '{print $13}' ). This output is stored into the variable $load ( => load=$(...) ).

grep: search a string

grep is a program that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can extract lines that contain a string or match a regex pattern. Let's imagine you want all external links from www.linuxintro.org's main page:

wget -O linuxintro.txt http://www.linuxintro.org
grep "http:" linuxintro.txt 

sed: replace a string

sed is a program that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can replace a string by another one. Let's imagine you want to print your distribution's name, but lsd_release outputs too much:

# lsb_release -d
Description:    openSUSE 12.1 (x86_64)

You want to remove this string "Description" so you replace it by nothing:

lsb_release -d | sed "s/Description:\t//"
openSUSE 12.1 (x86_64)

Once you understand regular expressions you can use sed with them:

  • to replace protocol names for a given port (in this case 3200) in /etc/services:
sed -ri "s/.{16}3200/sapdp00 3200/" /etc/services
  • if you have an apache web server here's how you get the latest websites that have been requested:

<source>

cat /var/log/apache2/access_log | sed ";.*\(GET [^\"]*\).*;\1;"

</source>

tr: replace linebreaks

sed is a program that is installed on almost all Linux distributions. It is a good helper for text stream processing. It can replace a character by another one, even over line breaks. For example here is how you remove all empty lines from your processor information: <source>

# cat /proc/cpuinfo | while read a; do ar=$(echo -n $a|tr '\n' ';')
if [ "$ar" <> ";" ]; then echo "$ar"; fi; done

</source>

wc: count

With the command wc you can count words, characters and lines. wc -l counts lines. For example to find out how many semicolons are in a line, use the following statement:

while read line
do echo "$line" | tr '\n' ' ' | sed "s/;/\n/g" | wc -l
done

It lets you input a line of text, counts the semicolons in it and outputs the number.

How does it do this?

It reads lines from your keyboard (while read line). It outputs the line (echo "$line"), but it does not output it in the console. The pipe (|) redirects the output to the input stream of the command tr. The command tr replaces the ENTER ('\ ') by a space (' '). The pipe (|) redirects the output to the input stream of sed. sed substitutes ("s/) the semicolon (;) by (/) a linefeed (\ ), globally (/g"). The pipe redirects the output to the input stream of the wc -l command that outputs the count of lines.

dialog: create dialogs

Dialog is a command that helps you creating dialogs in the shell. The answers given by the user are send to stderr and/or influence the command's return code. For example if you run this script:

#!/bin/bash
if (dialog --title "Message"  --yesno "Are you having fun?" 6 25)
then echo "glad you have fun"
else echo "sad you don't have fun"
fi

It will display this dialog:

Snapshot-dialog.png

This has been taken from http://www.linuxjournal.com/article/2807. Read there for more info.

See also

Share on Facebook