The Pickle Tickler The Pickle Tickler - 3 months ago 11
Bash Question

Why is the output of this bash script different every time it is run?

I have the following short bash script:

#!/bin/bash
cp /dev/null out.sh
while IFS=' ' read -r b c d e
do
echo -ne "sendevent " >> out.sh
echo -ne "/dev/input/event1 " >> out.sh
echo -ne "$(( 0x$c )) " >> out.sh
echo -ne "$(( 0x$d )) " >> out.sh
echo -ne "$(( 0x$e )) " >> out.sh
echo >> out.sh
done < "in.sh"


It takes in commands read from another script called
in.sh
in the form of
/dev/input/event1: 0003 0039 0000006d
, and I need it to output to another script called
out.sh
in the form of
sendevent /dev/input/event1 3 57 109
, where it converts the latter three terms from hexadecimal to decimal.

However, when I run the script, I get a quasi-broken output, which changes each time I run it. The correct output should be along the lines of:

sendevent /dev/input/event1 3 57 109
sendevent /dev/input/event1 3 53 40
sendevent /dev/input/event1 3 54 620
#and so on


Here's a picture of my actual output:

out.sh screenshot

Every time I run the script, the output is slightly different, and never uniform as I want it. Why does it output differently every time? How do I fix it so it outputs in the form I want it to?

I've tried adding in
sleep .01
after the last
echo
statement, and it did not fix the problem.

EDIT: As requested, here is a snippet of my
in.sh
:

/dev/input/event1: 0003 0039 0000006d
/dev/input/event1: 0003 0035 00000028
/dev/input/event1: 0003 0036 0000026c
/dev/input/event1: 0001 014a 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0039 ffffffff
/dev/input/event1: 0001 014a 00000000
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0039 0000006e
/dev/input/event1: 0003 0035 0000020f
/dev/input/event1: 0003 0036 000003dd
/dev/input/event1: 0001 014a 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0039 ffffffff
/dev/input/event1: 0001 014a 00000000
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0039 0000006f
/dev/input/event1: 0003 0035 000001b6
/dev/input/event1: 0003 0036 00000076
/dev/input/event1: 0001 014a 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0039 ffffffff


And here are two different outputs, run one after another, with the same
in.sh
:

sendevent /dev/input/event1 3 109
sendevent /dev/input/event1 3 54 620
sendevent /dev/input/event1 1 330 1
sendevent /dev/input/event1 0 0 0
sendevent /dev/input/event1 3 57 4294967295
/dev/input/event1 1 330 0
sendevent /dev/input/event1 0 0
sendevent /dev/input/event1 3 57 110
3 53 527
sendevent /dev/inp3 54 989
/dev/input/event1 1 330 1
sendevent 3 57 4294967295
sendevent /dev/input/event1 /event1 1 330 0
sendevent /dev/input/event1 0 0 0
sendevent /dev/input/event1 57 111
sendevent /dev/input/event1 3 53 438 sendevent /dev/input/event1 3 54
sendevent /dev/input/event1 1 1
sendevent
sendevent /dev/input/event1 57 4294967295
/dev/input/event1 1 330 /dev/input/event1 0 0 0
sendevent /dev/input/event1 3 57 112
sendevent /dev/input/event1 3 53
sendevent /dev/input/event1 3 54 881
sendevent /dev/input/event1
sendevent /dev/input/event1 3 57 t1 0 0 0 4294967295
sendevent 1 330 0
sendevent/dev/input/event1 0 0 0
113
sendevent /dev/input/event1 3 53
sendevent /dev/input/event1 54 901
/dev/input/event1 1 330 1
/dev/input/event1 0 0 0 sendevent /dev/input/event1 3 57 4294967295
330 0


And the second output:

sendevent /dev/input/event1 3 57 109
sendevent /dev/input/event1 3 53 620
sendevent /dev/input/event1 54 1 330 1 /dev/input/event1 0 0 0 sendevent /dev/input/event1 3 57
sendevent /dev/input/event1 1 330
sendevent /dev/input/event1 0 0 0
sendevent /dev/input/event1 3 57 110
/dev/input/event1 3 53
sendevent /dev/input/event1 54 989
/dev/input/event1 1 330
sendevent /dev/input/event1 0 0
sendevent 3 57 4294967295
sendevent /dev/input/event1 1 0
sendevent 0 0 0
put/event1 sendevent /dev/input/event1 3 111
sendevent 3 53 438
/event1 sendevent /dev/input/event1 3 54
sendevent /dev/input/event1 330 1
0 0 0
nt /dev/input/event1 sendevent /dev/input/event1 3 4294967295
sendevent 0
sendevent /dev/input/event1 0 0 0
sendevent /dev/input/event1 3 57 112
sendevent /dev/input/event1 3 53 247
sendevent /dev/input/event1 3 54 881 sendevent /dev/input/event1 1 330 1
/dev/input/event1 0 0
sendevent /dev/input/event1 57 4294967295
sendevent /dev/input/event1 1 330
sendevent /dev/input/event1 0 0 0
sendevent 3 57 113
sendevent 3 53 246
/event1 sendevent /dev/input/event1 3 54
sendevent /dev/input/event1 330 1
/dev/input/event1 0 0 0
/dev/input/event1 3 57 4294967295 sendevent /dev/input/event1 1 330
sendevent /dev/input/event1 0 0
sendevent 3 57 114
/event1 sendevent /dev/input/event1 3 53
sendevent /dev/input/event1 3 54 882 sendevent /dev/input/event1 1 1
sendevent /dev/input/event1 0 0 0 sendevent /dev/input/event1 3 sendevent /dev/input/event1 1 0
sendevent 0 0 0

Answer

The below uses an initial grep pass to sanitize input, avoiding potential shell injection attacks:

#!/bin/sh
grep '^[^ ]\+ [0-9a-f ]\+$' <in.sh | while IFS=' ' read -r _ c d e; do
  printf 'sendevent /dev/input/event1 %d %d %d\n' \
    "$(( 0x$c ))" "$(( 0x$d ))" "$(( 0x$e ))" 
done >out.sh

Note:

  • out.sh is only opened a single time, rather than reopened before every command that wants to write to it, and closed after the end of that individual command (as happens with a >> operator on each write).
  • Using printf once per line ensures that each write happens as an atomic syscall (when short enough lines are in use, as will reliably be the case here), and also avoids depending on behavior which the POSIX specification for echo explicitly declines to specify; the output of echo -n is explicitly undefined by the standard, whereas treating -e as an option rather than argument is actually contrary to the letter of the standard (and bash's default behavior doing so can be turned off, as with the combination of set -o posix and shopt -s xpg_echo).