LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Linux - Newbie (https://www.linuxquestions.org/questions/linux-newbie-8/)
-   -   bash: "cut" and "case" help please (https://www.linuxquestions.org/questions/linux-newbie-8/bash-cut-and-case-help-please-651500/)

tredegar 06-25-2008 04:18 AM

bash: "cut" and "case" help please
 
This has been driving me mad for 2 days now.

I am trying to automate the detection of a USB soundcard
Sometimes it is detected as card 0, sometimes as card 1.
I am writing a little script to restart alsa with the appropriate config file.
It looks like this, and is run as root

Code:

#!/bin/bash
cardnum=$(grep Bose /proc/asound/cards | cut -c 1)
case $cardnum in
"")    # No Bose
        cp /usr/share/alsa/alsa.conf.orig /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
0)    # Bose is card 0
        cp /usr/share/alsa/alsa.conf.bose0 /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
1)    # Bose is card 1
        cp /usr/share/alsa/alsa.conf.bose1 /usr/share/alsa/alsa.conf
        # Create link for dsp or realplay is confused
        rm /dev/dsp
        ln -sT  /dev/dsp1 /dev/dsp
        /etc/init.d/alsa-utils restart
        ;;
*)      echo ERROR in /etc/rc.local - soundcard number not known >> /var/log/messages
        ;;
esac
exit 0

But it doesn't work. I get "ERROR in rc.local - soundcard number not known"

This does work (Changes in red):

Code:

#!/bin/bash
cardnum=$(grep Bose /proc/asound/cards | cut -c 1)
case $cardnum in
"")    # No Bose
        cp /usr/share/alsa/alsa.conf.orig /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
0*)    # Bose is card 0
        cp /usr/share/alsa/alsa.conf.bose0 /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
1*)    # Bose is card 1
        cp /usr/share/alsa/alsa.conf.bose1 /usr/share/alsa/alsa.conf
        # Create link for dsp or realplay is confused
        rm /dev/dsp
        ln -sT  /dev/dsp1 /dev/dsp
        /etc/init.d/alsa-utils restart
        ;;
*)      echo ERROR in rc.local - soundcard number not known >> /var/log/messages
        ;;
esac
exit 0

So cardnum is not being set to "0" or "1" it is being set to a 2-or-more-character value (maybe the second character is a newline?) Why? Is there something wrong with my cut ? It makes no difference if I cut -c1 or cut -c 1 or cut -b1 or cut -b 1

[Yes, I know it's an ugly hack, and I should be using udev-rules, but ...]

Mr. C. 06-25-2008 04:45 AM

Include the value of cardnum in your output for the *) case, surrounded by quotes. You can always look at it under od if it is unprintable:

mycmd | od -bc

Try this program, and you'll spot your error. Pass it an argument, like 0, 1, 2, or nothing at all:

Code:

#!/usr/bin/bash

cardnum=$(echo $* | cut -c 1)

case "$cardnum" in
"")    echo No Bose
        ;;
0)    echo is card 0
        ;;
1)    echo is card 1
        ;;
*)      echo ERROR in rc.local - soundcard number not known
        ;;
esac


tredegar 06-25-2008 04:58 AM

Thanks for your reply Mr.C (I was wondering if you or tinkster would jump in first ;))
od - how very useful (I am rusty with bash)
Code:

tred@p4:~$ grep Bose /proc/asound/cards | cut -c 1  | od -bc
0000000 061 012 040 012
          1  \n      \n
0000004

So, not just one newline but two and an extra space (Explains why using 0?) Code goes here didn't work, but 0*) Code goes here did.
Can you explain what is going on?

Mr. C. 06-25-2008 04:59 AM

See my edit - i hit save too quickly.

tredegar 06-25-2008 05:19 AM

OK. Thanks for the edit.
Here we go:
Code:

tred@p4:~$ ./testcase
No Bose
tred@p4:~$ ./testcase 0
is card 0
tred@p4:~$ ./testcase 1
is card 1
tred@p4:~$ # I bet the next line doesn't work though
tred@p4:~$ ./testcase `grep Bose /proc/asound/cards | cut -c 1`
is card 1
tred@p4:~$ # It does!
tred@p4:~$

If I try to do cut twice, as in
Code:

tred@p4:~$  cardnum=$(grep Bose /proc/asound/cards | cut -c 1)
tred@p4:~$  echo $cardnum | cut -c 1 | od -bc
0000000 061 012
          1  \n
0000002
tred@p4:~$

It seems I am no better off. There is still a newline character there that I neither need nor want. I am just "not seeing it". Sorry

Mr. C. 06-25-2008 05:20 AM

Look very, very, carefully at the difference between your case in... statement and mine.

tredegar 06-25-2008 12:59 PM

Sorry for the delay - had to go to work.
Thanks for the hint, but....

I have looked very, very carefully at your case in statement and compared it to mine.

Yours has quotes around $cardnum mine doesn't. But I did try a variety of quote combinations over the last couple of days (around the variable name, and around the bits before the ")" in the case statement) and got precisely nowhere.

If I haven't yet exhausted your patience, please take look at this, which I think illustrates my problem well:

Code:

tred@p4:~$ cat testcase
#!/bin/bash
rawline=$(grep Bose /proc/asound/cards)
echo '$rawline' is '"'$rawline'"'
cardnum=$(grep Bose /proc/asound/cards| cut -c 1)<--There is no trailing space on this line
# cardnum=$(echo $* | cut -c 1)
echo '$cardnum' is '"'$cardnum'"'
echo and this is what it really looks like when piped through od -bc
echo $cardnum| od -bc
echo Now we will test cardnum to see if it is '"'1'"'

case "$cardnum" in
"")    echo No Bose
        ;;
0)    echo is card 0
        ;;
1)    echo is card 1
        ;;
*)      echo ERROR in rc.local - soundcard number not known
        ;;
esac

tred@p4:~$ ./testcase
$rawline is "1 [Audio ]: USB-Audio - Bose USB Audio Bose Corporation Bose USB Audio at usb-0000:00:03.1-1, full speed"
$cardnum is "1 " <<-notice the extra space after the 1 and before the closing quote
and this is what it really looks like when piped through od -bc
0000000 061 012
          1  \n  <-- WTF? A space is now a newline, or maybe not
0000002
Now we will test cardnum to see if it is "1"
ERROR in rc.local - soundcard number not known
tred@p4:~$ #Grrrrrrr. Time to admit humiliating defeat. Back to LQ ...

How can cardnum be both a 1 and a 1-followed-by-a-space and a 1-followed-by-a-newline (but I think the /n in the output of od was put there by the echo command. So maybe cardnum is only being two things at once. (Panic: is bash quantum?)

The only time this script does as it should is when I unplug the speakers, and it correctly returns "No Bose".

I made it print out rawline for you in case you wanted to test this script for yourself.

I expect I'll be kicking myself soon, but I just do not understand what is going wrong.

Any ideas? I appreciate your input.

Mr. C. 06-25-2008 02:14 PM

Something's not right here. I perform the same test, using a file with your contents, and your code. It runs as expected:

Code:

$ cat confused.sh
#!/bin/bash

infile=/tmp/cards

rawline=$(grep Bose $infile)
echo '$rawline' is '"'$rawline'"'
cardnum=$(grep Bose $infile | cut -c 1) #--There is no trailing space on this line
# cardnum=$(echo $* | cut -c 1)
echo '$cardnum' is '"'$cardnum'"'
echo and this is what it really looks like when piped through od -bc
echo $cardnum| od -bc
echo Now we will test cardnum to see if it is '"'1'"'

case "$cardnum" in
"")    echo No Bose
        ;;
0)    echo is card 0
        ;;
1)    echo is card 1
        ;;
*)      echo ERROR in rc.local - soundcard number not known
        ;;
esac

$ ./confused.sh
$rawline is "1 [Audio ]: USB-Audio - Bose USB Audio Bose Corporation Bose USB Audio at usb-0000:00:03.1-1, full speed"
$cardnum is "1"
and this is what it really looks like when piped through od -bc
0000000  061 012                                                       
          1  \n                                                       
0000002
Now we will test cardnum to see if it is "1"
is card 1

The newline you see is coming from echo itself; it is not contained in the variable. Use -n to prevent:

Code:

$ cardnum=$(grep Bose cards | cut -c 1)       
$ echo $cardnum | od -bc
0000000  061 012                                                       
          1  \n                                                       
0000002
$ echo -n $cardnum | od -bc
0000000  061                                                           
          1                                                           
0000001

Look at od -bc of your cards file.

estabroo 06-25-2008 03:11 PM

I don't know why you are getting the extra character in cut, but you could just use a substring operation in the shell to get that first character.

#!/bin/bash
rawline=$(grep Bose $infile)
cardnum=${rawline:0:1}

...

Mr. C. 06-25-2008 04:35 PM

You're right estabroo, at some point you have to throw in the towel. But not too early!

Otherwise, we miss the opportunity to learn important concepts in an environment, such as newline->space translation during shell variable assignment, echo and the actual stream of bytes output (and how -e, -n affects), tools like od to examine the data, shell variable quoting (and how lack of quoting essentially alters the appearance of a language statement), etc. All good learning opportunities here.

tredegar 06-25-2008 05:01 PM

Thanks for your input estabroo.
I have an uneasy feeling that Mr.C knows the answer to my problem and he is quite properly trying to "lead the horse to water" but it (I) won't drink.

@Mr.C
Quote:

Something's not right here. I perform the same test, using a file with your contents, and your code. It runs as expected:
OK. But I am annoyed that you can run it and I cannot.

May I ask, what distro are you using? I am running kubuntu 6.06 on the PC I am having problems with. 6.06 is "Long Term Support", and up to date.

I'll try running these scripts on some other distros, when I can find time (=now + 1 week).
And I'll do an od -bc of my cards file (or more likely the output of grep Bose /proc/asound/cards because I am still not sure where the problem lies.
Until later, and thanks
:)

Mr. C. 06-25-2008 05:28 PM

Both NetBSD, and a hand-built 2.4-based linux box. But the important point would be the shell version. It is doing the work.

$ echo $BASH_VERSION
3.2.33(1)-release

$ echo $BASH_VERSION
3.2.1(1)-release


Add

Code:

echo LENGTH: ${#cardnum}
just after you assign cardnum to see its length. And, btw, trailing spaces after your command substitution in the assignment to cardnum would be ignored, so you don't have to worry about them. To include spaces, you'd need them protected by quotes.

Some other considerations - grep is *line* oriented, and lines require proper terminators. The main difference between your environment and mine is that you have the raw data from grep Bose /proc/asound/cards; I had to create a look-alike to test file. Get an od -bc of that /proc/asound/cards file.

Your results from:


Code:

tred@p4:~$ grep Bose /proc/asound/cards | cut -c 1  | od -bc
0000000 061 012 040 012
          1  \n      \n
0000004

are consistent with

Code:

tred@p4:~$  echo $cardnum | cut -c 1 | od -bc
0000000 061 012
          1  \n
0000002

because in the assignment to a shell variable (eg. cardnum), newlines and extra whitespace between successive lines is converted to a single space, and trailing whitespace is removed:
Code:

$ echo -ne '1\n \n' | od -bc
0000000  061 012 040 012                                               
          1  \n      \n
0000004

$ cardnum=$(echo -ne '1\n \n')

$ echo  $cardnum | od -bc 
0000000  061 012                                                       
          1  \n                                                       
0000002


tredegar 06-26-2008 02:08 PM

I finally cracked it.
I was at work, so only able to play with linux on my laptop.
No USB speakers, but I could identify its single soundcard as 0 and the script worked.
So when I got home I copied it to my desktop, changed the grep from HDA-Intel to Bose and it didn't work.

Here's the moment of enlightenment, on my desktop:
Code:

tred@p4:~# cat /proc/asound/cards
0 [Audio          ]: USB-Audio - Bose USB Audio
                    Bose Corporation Bose USB Audio at usb-0000:00:03.1-1, full speed
1 [SI7012        ]: ICH - SiS SI7012
                    SiS SI7012 with AD1980 at 0xa400, irq 209
tred@p4:~# grep "Bose" /proc/asound/cards
0 [Audio          ]: USB-Audio - Bose USB Audio
                    Bose Corporation Bose USB Audio at usb-0000:00:03.1-1, full speed
tred@p4:~# grep "Bose" /proc/asound/cards | cut -c1
0
  <-- And here is an apparently blank line. It should not be there
tred@p4:~# grep "Bose" /proc/asound/cards | cut -c1 | od -bc
0000000 060 012 040 012
          0  \n      \n <-- a 0 then newline then space then newline. Bad
0000004
tred@p4:~#
tred@p4:~# grep ": USB-Audio" /proc/asound/cards | cut -c1 | od -bc
0000000 060 012
          0  \n  <--Much better
0000002
tred@p4:~#


So my error was not choosing a unique identifier for the line that begins with the soundcard number.
The grep "Bose" returned two lines (and I did not realise that the cut would work on both of those lines. Hence the incorrect cardnum not being allocated just "" "0" or "1"

So here is the (rc.local) script that finally works properly (until another unforseen situation crops up):

Code:

#!/bin/bash -e
#
# Simple control of Bose USB speakers
# If they are plugged in at boot, they'll be used,
# otherwise the internal soundcard will be used.
#
# It may fail if anything is using the sound card when it is called.
# Sometimes the Bose is allocated as card 1, sometimes as card 0
# So it gets a bit more complicated
# I wish I knew how to get udev to handle this (Homework required)

foo=$(grep ": USB-Audio" /proc/asound/cards | cut -c 1)

case $foo in

"")    # No Bose
        cp /usr/share/alsa/alsa.conf.orig /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
0)      # Bose is card 0
        cp /usr/share/alsa/alsa.conf.bose0 /usr/share/alsa/alsa.conf
        /etc/init.d/alsa-utils restart
        ;;
1)      # Bose is card 1
        cp /usr/share/alsa/alsa.conf.bose1 /usr/share/alsa/alsa.conf
        # Create link for dsp or some apps are confused
        rm /dev/dsp
        ln -sT  /dev/dsp1 /dev/dsp
        /etc/init.d/alsa-utils restart
        ;;
*)      echo ERROR in rc.local - unknown soundcard number >> /var/log/messages
        ;;
esac
exit 0

Mr.C, thanks for your patience, suggestions, and especially for reminding me about od (very useful)

Now I can go off and add a correction to a previous post ( http://www.linuxquestions.org/questi...efault-649705/ ) about getting USB speakers recognised and auto-configured.

Mr. C. 06-26-2008 02:39 PM

Always verify your input data, and check your commands in small pieces before chaining long pipelines, esp. with pattern matching.

Change your grep to FORCE looking for a line that starts with a number. Always use as restrictive an RE match as necessary to reduce chances of other unexpected matches: Eg.:

grep '^[0-9].*USB-Audio' cards


Good job.

tredegar 06-26-2008 04:31 PM

Quote:

grep '^[0-9].*USB-Audio' cards
Ah! An altogether neater solution.
You have shown me something(s) useful for the future.
Thanks again
;)


All times are GMT -5. The time now is 05:45 AM.