Gregory Deal on 30 May 2016 18:17:43 -0700


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Re: [PLUG] Bash question


Thanks for the info. A dump of the text going into the readarray command shows the first character is a new line. So since splitting on newline (IFS= ), I'm now not surprised that the first element is a null, and an array length check is misleading. I chose to make the empty decision back on the raw text. I got the readarray style from a stack exchange/stackoverflow site example that was voted up by a non-trivial amount, but I can see the concern over losing subshell variables. I'm not familiar with the loop < <(command) style.


From: "Christopher Barry" <christopher.r.barry@gmail.com>
To: plug@lists.phillylinux.org
Sent: Monday, May 30, 2016 5:30:19 PM
Subject: Re: [PLUG] Bash question

On Mon, 30 May 2016 12:14:51 -0400
JP Vossen <jp@jpsdomain.org> wrote:

>On 05/29/2016 09:22 PM, Christopher Barry wrote:
>> On Sun, 29 May 2016 17:26:58 +0000 (UTC)
>> Gregory Deal <1deal@comcast.net> wrote:
>>  
>>> Even after checking the "learning bash" book and searching, I'm
>>> having a little trouble explaining some bash functioning. I'm
>>> trying to process some command output, making an array of its
>>> output lines. I was trying to check for the "no output" case (which
>>> goes right back to a prompt) by interrogating the final array
>>> length. But even with no command output, I get a length of 1. Could
>>> someone explain this? Thanks.
>>>
>>> Script:
>>>
>>> #!/bin/bash
>>>
>>> # DTD specifies 0 or 1 <rules>, so check if any before processing.
>>> echo "Rules content:"
>>> xmlpathval.py ~/program1/sample2.xml "/sync/rules[1]/text()" |
>>> while IFS= read -r line; do
>>> echo "-->'$line'"
>>> done
>>> echo "IFS='$IFS'"

As a rule, you almost never want to pipe stuff into a while loop. The
reasoning is you've created a subshell by doing this, and any vars set
in the loop are discarded after the loop (and subshell) terminates. Use
a process substitution instead, e.g.

while read foo; do
    echo "${foo}"
    foo_array+=( "${foo}" )
done < <(generate_foo_lines)

# foo_array exists after loop terminates
echo "${foo_array[@]}"

I realize you're only echo-ing as you read the lines here, but still,
the pipe-into structure is a construct you should try to avoid.

>>>
>>> rulesInfo=$(xmlpathval.py ~/program1/sample2.xml
>>> "/sync/rules[1]/text()") <<<<<<< raw output shown below if [[ -z
>>> $rulesInfo ]]; then echo "No rules found."
>>> fi
>>> echo "rulesInfo='$rulesInfo'"
>>>
>>> IFS=
>>> unset rulesArray
>>> readarray -t rulesArray <<< "$rulesInfo"
>>> echo "length=${#rulesArray[@]}"
>>> if [[ ${#rulesArray[@]} -eq 0 ]]; then
>>> echo "empty"
>>> else
>>> echo "not empty"
>>> fi
>>> echo "rulesArray(single echo)=${rulesArray[@]}"
>>> echo rulesArray from loop=
>>> for element in "${rulesArray[@]}"
>>> do
>>> echo "'$element'"
>>> done
>>>
>>> Input:
>>>
>>> gkd@sisko:~/program1$ xmlpathval.py sample2.xml
>>> "/sync/rules[1]/text()" gkd@sisko:~/program1$ echo $?
>>> 0
>>>
>>> Results :
>>>
>>> gkd@sisko:~/scripts$ ./test.sh
>>> Rules content:
>>> IFS='
>>> '
>>> No rules found.
>>> rulesInfo=''
>>> length=1
>>> not empty
>>> rulesArray(single echo)=
>>> rulesArray from loop=
>>> ''
>>> gkd@sisko:~/scripts$
>>>
>>>  
>>
>> t=( '' )
>> echo ${#t[@]}
>> 1
>>
>> yet,
>>
>> [[ ${t[@]} ]] && echo yes
>> <no output>
>>
>> therefore,
>>
>> test for an actual value in ${rulesArray[@]} as well
>>
>> (( ${#rulesArray[0]} )) && [[ "${rulesArray[@]}" ]] && do stuff  
>
>
>May be useful:
>* http://www.gnu.org/software/bash/manual/html_node/Arrays.html
>* http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_02.html
>* http://www.thegeekstuff.com/2010/06/bash-array-tutorial/
>
>As shown in your `echo "length=${#rulesArray[@]}"` you are getting
>*something* into that array.  I'd try `echo "length=${#rulesArray[@]}"
>| hexdump -C` to see what it is, though I suspect a trailing "\n" is
>creating an empty element.

his 'readarray -t' strips the trailing newline. Pretty sure the data in
his array is the null string, as shown in my simple example above using
the array ${t[@]}.

>
>That said, you could probably use something like this to test:
>        if [ "${#rulesArray[@]}" -eq 0 -a -n "${rulesArray[@]}" ]; then
>Or even simpler
>        if [ -n "${rulesArray[@]}" ]; then

In "bash" today, '[' is deprecated, and '[[' is preferred for many, many
reasons. '[' is for sh, which is rarely actually needed anymore except
on very old systems, or when POSIX compliance is being forced. The fact
that he's using readarray says to me that POSIX compliance is not a high
priority.

repeated from previous post here:

 (( ${#rulesArray[0]} )) && [[ "${rulesArray[@]}" ]] && do stuff

is likely the simplest solution to this problem.

 (( ${#rulesArray[0]} ))
returns true if the var between double-parens evaluates to a non-zero
integer value.

 [[ "${rulesArray[@]}" ]]
returns true if the var evaluates to a non-null string value.

Notice that no switches inside the (( )) or [[ ]] are needed for this.

>
>If there is a first element that is not empty...well then you are "not
>empty".  See `help test` for the meanings of "-a", "-n", etc.  (`help`
>is "man" for bash internal commands. :)
>
>I have to admit I find bash arrays very confusing because of all the
>"random" punctuation.  I want to add a bunch more on that stuff
>if/when we ever get around to the 2nd edition of the _bash Cookbook_.
>
>You might also look at `help read` and the "-a array" option.

He's using 'readarray' (in one block of code), the bash builtin
designed for this purpose.


As an aside, insisting upon sh or POSIX compatibility at all
times, and eschewing the great benefits and advances made in recent bash
versions is mostly inappropriate today. Unless you absolutely must be
compatible with sh for some legacy installation without bash, or the
host is running a version of bash earlier than v4.x, continuing to
write bash scripts as clunky sh code, like it's still 1995, is missing
out on a lot of the power and flexibility that bash has to offer
today IMHO.


maybe something like this? (obviously not tested with your input...)

#!/bin/bash
# show rules if they exist
declare -a rulesArray=()
readarray rulesArray < <( xmlpathval.py \
                         ~/program1/sample2.xml "/sync/rules[1]/text()"
                        )
(( ${#rulesArray[0]} )) && [[ "${rulesArray[@]}" ]] && {
    echo -e >&2 "Rules:\n${rulesArray[@]}"
} || {
    echo >&2 "No rules."; false
}
exit $?


I've been hacking bash for two decades and love pushing it's envelope.
Some examples of my decidedly non-compliant hackery can be found here:
https://github.com/christopher-barry

--
Regards,
Christopher
___________________________________________________________________________
Philadelphia Linux Users Group         --        http://www.phillylinux.org
Announcements - http://lists.phillylinux.org/mailman/listinfo/plug-announce
General Discussion  --   http://lists.phillylinux.org/mailman/listinfo/plug

___________________________________________________________________________
Philadelphia Linux Users Group         --        http://www.phillylinux.org
Announcements - http://lists.phillylinux.org/mailman/listinfo/plug-announce
General Discussion  --   http://lists.phillylinux.org/mailman/listinfo/plug