Thursday, May 6, 2021

Bash while Loop and Its Strange Behavior with Stdin


Hi there. In this post, I will describe an interesting behavior, that I noticed in a while loop in one of my bash scripts about 5 years ago. I recently encountered something similar again. Fortunately, I solved it easily this time, but I think it deserves to be investigated in a post. So, what is this interesting behavior?

Let's consider a simple bash script with a while loop:

# first example
while read -r line; do
    echo $line
    sleep 3
done < input.file

input.file is a simple text file which contains more than one lines. For example, a file which was simply created with ls -1 > input.file command. The script does read these lines from the file line by line and prints them with 3 second intervals. So far, so good. Another script, that does the same in a different way:

# second example
cat input.file | while read line; do
    echo $line
    sleep 3
done

Even though the code is different, they are the same in the sense of black box approach according to its input and output. Finally, another example which does the same thing, too:

# third example
for line in $(cat input.file); do
    echo $line
    sleep 3
done

Even though a while loop is not used above, it still is an alternative to the previous examples as it generates the same output from the same input.

Nothing strange so far, but the strange behavior will show up, when I try to read from stdin in the loop. I read the input from stdin using read command. For example, I wait for a keypress after each line of input.file. In order to do this, I replace sleep command with read -t 10 -n 1 dummyvariable. The parameter t is the input timeout in seconds and the parameter n is the character count of the input. The variable name is not important because I will not use it anywhere. As mentioned before, my input files consists of ls -1 output (12 lines).

Arduino
Desktop
Documents
Downloads
Music
Pictures
Public
Templates
Videos
input.file
logs
test.sh

The execution of the script below should take 120 seconds from 10 seconds per line, if I don't press any key. The script, I am running:

while read -r line; do
    echo $line
    read -t 10 -n 1 dummyvariable
done < input.file

But this is not executed as I expected. All the first characters of all lines got deleted except the first line and it ended in less than a second. But why?

To inspect the execution, I inserted a sleep 10 command under read and opened another bash tab.

[user@host ~]$ ps aux | grep test | grep -v grep
user   12001  0.0  0.0 222344  3612 pts/1    S+   16:23   0:00 /bin/bash ./test.sh

[user@host ~]$ ls -la /proc/12001/fd
[SNIP]
lr-x------. 1 user user 64 Apr 29 16:26 0 -> /home/user/input.file
lrwx------. 1 user user 64 Apr 29 16:26 1 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 16:26 10 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 16:26 2 -> /dev/pts/1
lr-x------. 1 user user 64 Apr 29 16:26 255 -> /home/user/test.sh

When I checked the file descriptors of the running process, I saw that the zeroth descriptor, which has to be normally stdin, is redirected to input.file and this is actually me, doing it. As soon as I gave "input.file" as input to while, stdin changed. Therefore, I cannot use stdin as input source in while anymore.

This is not a problem or an error. That's why, I avoided using the word "problem" since the beginning of this post. This is totally an expected behavior, but still strange when a file descriptor just changes within a loop, considering loop practices of other programming languages like python or C.

When I replace sleep command with read in the second example, same behavior occurs in a different way. First line printed normally, but first characters are still missing in the rest of output. In this example, I used pipe instead of IO redirection but this resulted redirection of stdin to a pipe instead of a file under the hood. To examine this, I inserted a sleep command again after read command, ran again and checked file descriptors:

[user@host ~]$ ps aux | grep test | grep -v grep
user   15635  0.0  0.0 222212  3340 pts/1    S+   19:01   0:00 /bin/bash ./test.sh
user   15637  0.0  0.0 222212  1996 pts/1    S+   19:01   0:00 /bin/bash ./test.sh

[user@host ~]$ pstree -p | grep test
 | |-konsole(10108)-+-bash(10120)---test.sh(15635)---test.sh(15637)---sleep(15668)

[user@host ~]$ ls -la /proc/15635/fd   /proc/15637/fd
/proc/15635/fd:
[SNIP]
lrwx------. 1 user user 64 Apr 29 19:03 0 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 19:03 1 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 19:03 2 -> /dev/pts/1
lr-x------. 1 user user 64 Apr 29 19:03 255 -> /home/user/test.sh

/proc/15637/fd:
[SNIP]
lr-x------. 1 user user 64 Apr 29 19:03 0 -> 'pipe:[289548]'
lrwx------. 1 user user 64 Apr 29 19:03 1 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 19:03 2 -> /dev/pts/1

The main difference here is, that test.sh is executed multiple times. PID 15635 runs cat. Then the spawned process with ID 15637 executes while loop and the output of first process is sent to the stdin of the second process via pipe. In short, although the algorithm is different, it basically produces the same result.

In this third example, there are no pipe nor redirect. Using cat, the contents of input.file is dumped to memory and processed by for loop. As it can be seen below, the descriptors are not changed.

[user@host ~]$ ps aux | grep test
user   15955  0.0  0.0 222212  3336 pts/1    S+   19:13   0:00 /bin/bash ./test.sh

[user@host ~]$ ls -la /proc/15955/fd
[SNIP]
lrwx------. 1 user user 64 Apr 29 19:13 0 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 19:13 1 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 19:13 2 -> /dev/pts/1
lr-x------. 1 user user 64 Apr 29 19:13 255 -> /home/user/test.sh

Hence, a for loop might be preferable over while in some cases. Below is another example of such behavior:

cat ^testuser /etc/passwd | cut -d':' -f 1 | while read -r line; do
    sudo passwd $line
done

Let's assume, there are three users in this system named testuser01, ...02, ...03. When trying to change their passwords, passwd testuser01 command is executed at first iteration, then the string "testuser02" is entered as a password to testuser01 (as an input to passwd testuser01 command) and when passwd asked for the same password second time (for confirmation), "testuser03" is entered as input and so passwd fails. The loop is completed but passwd command has only been executed once.

So, how can this be done with while? In this case, file descriptor needs to be assigned manually. Frankly, I have only used this technique once before for a different purpose: Read n lines at a time using Bash. What's done here is a bit advanced but quite elegant at the same time as it shows how flexible bash is. By the way, the accepted answer is IMHO too complex to solve "read n lines" problem (see other answers) but same approach can be applied to this situation, I am mentioning: In the first example above, zeroth descriptor (which is stdin normally) was redirected to the file and stdin was allocated with tenth descriptor. In that case, I can guarantee that stdin gets the zeroth descriptor by opening the file with another descriptor using the method described in the answer.

exec 10< input.file
while read line <&10; do
    echo $line
    read -t 10 -n 1 dummyvariable
done

Below is a snapshot of the file handles while the script is running:

[user@host ~]$ ps aux | grep test
user   17537  0.0  0.0 222212  3408 pts/1    S+   20:26   0:00 /bin/bash ./test.sh

[user@host ~]$ ls -la  /proc/17537/fd
[SNIP]
lrwx------. 1 user user 64 Apr 29 20:26 0 -> /dev/pts/1
lrwx------. 1 user user 64 Apr 29 20:26 1 -> /dev/pts/1
lr-x------. 1 user user 64 Apr 29 20:26 10 -> /home/user/input.file
lrwx------. 1 user user 64 Apr 29 20:26 2 -> /dev/pts/1
lr-x------. 1 user user 64 Apr 29 20:26 255 -> /home/user/test.sh

The file was opened with tenth descriptor, just as I wanted and read command could read data from stdin successfully. 

Thursday, March 18, 2021

Hantek 6022BL USB Oscilloscope: OpenHantek+PulseView vs. Hantek


Hi there. About one and a half years ago, I bought myself a Hantek 6022BL USB oscilloscope. Until today, I was using Hantek's own software in Windows and I was not very happy with it. As it can be seen from my previous article, I recently reinstalled my computer and in the meantime, I had the chance to check alternatives of Hantek. I tested and reviewed OpenHantek as oscilloscope software as well as PulseView as logic analyzer software. In this article, I will explain how to install device drivers and will compare these programs. Last but not least, this will also be like a note to myself for driver installation.

Hantek 6022BL combines a two-channel USB oscilloscope with a 16-channel logic analyzer. With its 20 MHz bandwidth and 48 MS/s sampling rate, it meets the needs of an  average electronics hobbyist. With respect to its properties, it is obviously not a professional bench-top model. It was attractive to me just because it is small and cheap.

Since USB oscilloscopes are available, there has been a constant debate about their usefulness. Many forums are divided between USB supporters and benchtop'ers similar to Android vs. iOS debate. To be honest, there are much better quality USB oscilloscopes than Hantek. For example, Picotech produces very high quality devices but their 2205A MSO model which has comparable features to Hantek, costs 4 times more. By the way, Hantek devices don't have USB isolation according to an Amazon review. This means, if the probe accidentally touches any high voltage source, it will be conducted to mainboard. Picotech models (AFAIK all of them) and some Owon models have isolated inputs. In my opinion, USB oscilloscopes are serious competitors to benchtop models due to their relatively low prices but this is limited to entry level. And nobody would invest in an USB oscilloscope for a high end lab grade device.

Setting this device up on Fedora is really piece of cake. Therefore, I will start with configuring the device on Fedora. There is a button on the back of the oscilloscope where USB connectors are. First of all, this button needs to be pressed (position H). In this position, USB ID of oscilloscope becomes 04B4:602A.


From my understanding, the USBXI port (above) is a modified USB port, maybe proprietary design, and allows to cascade multiple devices. In other words, using USBXI, it is possible to cascade connect two Hantek 6022BLs in order to use it as a four channel oscilloscope or 32 channel logic analyzer.

A Y-shaped cable, shown in the next image, comes with the oscilloscope. Two inputs allow, current can be drawn from both ports when current from single port is not enough. I also connected the oscilloscope with a standard B-type USB cable (with two ends) without problem. The red plug is just for power, has no data pins. It can be connected to an USB adapter, too.

Hantek did not develop a linux version of its software. OpenHantek is a really good alternative for linux because it is in Fedora standard repository, which means a simple sudo dnf install openhantek command is enough to install it. Likewise, its deb file is in standard Ubuntu repo, which means Ubuntu and Mint users wouldn't have any problem to install it. Even though OpenHantek is open source, I haven't tried to compile it.

If the oscilloscope is connected to the computer, oscilloscope windows opens immediately, when OpenHantek is started. Otherwise, a window appears first to the user to select a compatible device to acquire signals. I have attached a screenshot of the oscilloscope interface, below:


Many standard oscilloscope features are available on the user interface. For the screenshot, I set the calibration signal to 1 kHz and attached it to the first oscilloscope channel. I have connected an external signal generator to the second channel and chose the trigger as the first channel. When I put the markers on onto two consecutive leading edges of the square wave, the frequency can be accurately read as 1 kHz/div at the top of the screen. The dark blue line on the bottom is Fourier (spectral) analysis of the sine wave. Tiny harmonics at 6 and 8 kHz are clearly visible. The screenshot below, shows a Lissajous curve created with a simple RC circuit:


Signal histogram can be added from the left pane and the zoom button on the left pane allows zooming 2x2 divisions in the middle in a separate window. There are addition, subtraction and multiplication operations in math mode and finally, signal values can be exported as .csv file.

Since OpenHantek doesn't support logic analyzer, sigrok's PulseView (PV) needs to be installed separately. Actually, PV can also visualize analog signals but it does not have many options like OpenHantek. The installation of PV is relatively easy. Rpm packages for Fedora can be downloaded from Downloads page. PV is open source, too. I tried to compile it but gave up later, because it took longer than I expected. Following packages are prerequisites for PV: libzip, qt5-qtbase, qt5-qtbase-gui, boost-serialization, qt5-qtsvg. Fedora 33 do not only have boost-serialization installed. Once prerequisites are installed, the software packages must be installed with the same order as they appear on Downloads page. But careful: Some links contain more than one rpm. They can be installed using following snippet:

sudo dnf install libzip qt5-qtbase  qt5-qtbase-gui  boost-serialization  qt5-qtsvg

sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/libserialport/0.1.1/9.fc33/x86_64/libserialport-0.1.1-9.fc33.x86_64.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/libsigrok/0.5.2/4.fc33/x86_64/libsigrok-0.5.2-4.fc33.x86_64.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/libsigrokdecode/0.5.3/6.fc33/x86_64/libsigrokdecode-0.5.3-6.fc33.x86_64.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/sigrok-cli/0.7.1/5.fc33/x86_64/sigrok-cli-0.7.1-5.fc33.x86_64.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/libsigrok/0.5.2/4.fc33/x86_64/libsigrok-cxx-0.5.2-4.fc33.x86_64.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/sigrok-firmware/0.1.0/16.20151211gitb2daf81.fc33/noarch/sigrok-firmware-filesystem-0.1.0-16.20151211gitb2daf81.fc33.noarch.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/sigrok-firmware-fx2lafw/0.1.7/3.fc33/noarch/sigrok-firmware-fx2lafw-0.1.7-3.fc33.noarch.rpm
sudo rpm -ivh  https://kojipkgs.fedoraproject.org//packages/pulseview/0.4.2/4.fc33/x86_64/pulseview-0.4.2-4.fc33.x86_64.rpm

PV runs when the button is in P (not pressed) position. First, the device must be disconnected, otherwise the button has no effect. USB ID does not change on the fly. VID/PID of the device becomes 0925:3881 when the button is in P position and it starts running with Saleae Logic chipset (or compatible). Like OpenHantek, PV automatically recognizes connected devices and selects appropriate driver. If it shows "<No Device>", just click on it and select "fx2lafw" driver. Although there is a "hantek-6xxx" driver on the list, since the button is in P position, it doesn't run with Hantek chipset anymore. This "hantek-6xxx" driver allows PV to run in oscilloscope mode as I mentioned previously, when the button is in H position. After selecting the correct driver, click "Scan for devices..." and "Saleae Logic with 8 channels" is detected. At this point, PV is a little disappointing because the driver supports only 8 channels (Source: Hantek 6022BL). I hope, this will be improved in the future. The best thing with PV is that it has a protocol decoder.

I had nothing handy to demonstrate with logic analyzer. I found an Arduino project developed by Z. X. Lee in internet. He tested Saleae's logic analyser in his own blog. In case the project is deleted in future, its .ino file can also be downloaded here. After uploading the file to Arduino and making the connections (Arduino pins 5..12), I set the sampling parameters to 24 MHz and 100M samples and start sampling by pressing "Run" at the top left. In the mean time, I press the green-yellow button on the rightmost and add a parallel protocol decoder.


With the dark blue "Marker" button, left to the device selection field, the intervals between signal transitions and the signal frequency can be measured. PV doesn't have much more than that. 


Now, I will explain how to configure these in Windows. First of all, I have two alternatives in Windows: Either I use OpenHantek+PV in Windows, too but in that case, I will never be able to use 16 channels of the logic analyzer. Or, I use Hantek's own software. By the way, this software doesn't have a special name. The name of its shortcut is "Hantek6022BL". So, I will mention it HOS (Hantek's Own Software) in the rest of the article, so that there is no confusion between the device and its software. I agree, this abbrevitation is not creative at all but Hantek had to be creative not me. Another interesting thing is, that OpenHantek and HOS use different USB device drivers. I cannot simply install and use both. Therefore, I decided to use HOS anyway because I can use others with my other machine whenever I want.

When using the oscilloscope with HOS, the button must be in H position. I checked this, connected it to my computer and installed Windows drivers from the CD that came shipped with the oscilloscope. I didn't have a problem at this step but if the drivers cannot be installed in Windows 10 because they are not signed, there is an unpleasant workaround.  By the way, "HT6022BL Software" file on Hantek's product page contains newer drivers than "HT6022BL Driver" file. As of February '21, the content on product CD is the same as "HT6022BL Software" file. After installing the device drivers, I ran the software installation on CD and started HOS after installation is finished.  To be honest, the interface gives the impression that it's very low quality compared to OpenHantek.

I clicked on 6022BE icon for oscilloscope interface and my personal opinion is that the interface is not useful at all.


The first thing is that the right panel is simpler than it has to be. The user needs to find correct button for the frequently used "markers". The only feature, I find useful is that "Auto" button above, which automatically adjusts appropriate voltage, trigger and time interval for incoming signal. There is A/B operation in math mode, additionally. I could not understand its Fourier analysis mode, only a single peak shown up at the left edge of the screen for a 2 kHz signal and it couldn't capture harmonics like OpenHantek did. How can I know, that peak corresponds to 2 kHz if I am measuring an unknown signal. Unlike OpenHantek, there is neither zoom nor histogram. When I press Ctrl+M, a measure panel shows up, which contains useful signal information (like max./min. voltage, peak to peak voltage, frequency etc.) but its design is still insufficient. For example, if this information from both channels were listed side by side, more data would fit in the window and it would be easier to compare.


Finally, I tried to show an error I found in the interface in the image above. Controls disappear on the right pane, when it is made narrow and made wide again without scrolling the pane to left.

So, I closed the oscilloscope interface and clicked 6022BL icon for logic analyzer. The program started sampling as soon as it was started, yet I had set neither the sampling rate nor the number of samples! As you can see, HOS is quite unsuccessful about usefulness.


I set sampling rate to 100K/sec from the right panel. However, sample length cannot be changed. This is unfortunately another imperfection of the program. I found out its reason long after I bought the device. On the page 66 of its manual, it is written "Buffer Size: Max. 1M Samples". The developers of HOS appearantly preferred to stop sampling when its buffer is full while PV constantly fetches buffered data and purges device buffer. Sampling is started with play like button on the top right. The fact that color of each channel is same reduces legibility and this color is not a setting that can be changed.

Well, why should we use HOS, if its so bad? Shortest answer is, I wouldn't be using it if I had only a Windows computer. As I explained above, I cannot use it with linux anyway. So I already have OpenHantek+PV installed on my linux machine and I keep HOS just as an alternative. I already checked OpenHantek on Windows as well. Now, my only concern is switching the drivers because as I mentioned before, OpenHantek and PV use WinUSB driver, whereas HOS uses the driver written by Hantek. There is no need to uninstall Hantek driver to switch to OpenHantek. An utility called zadig deactivates Hantek driver and installs WinUSB driver. Zadig comes bundled with Windows  installer of PV.   The usage of zadig is explained in OpenHantek's installation guide under "Windows USB Driver Install".

If I may explain briefly, I start zadig and select Hantek6022BL from the list above. If it is not in the list, "List All Devices" must be enabled under Options menu. I took the screenshot on the left from a machine which has WinUSB already installed, normally WinUSB will not appear in "Driver" field. After clicking "Install Driver" button, WinUSB gets installed but this process has to be repeated twice. After doing this, the oscilloscope gets listed under "Universal Serial Bus devices" in device manager. Both Zadig and PV need VC++2010 library to be installed, otherwise they return "MSVCR100.DLL not found" error.

 
Hantek Driver

 
 
WinUSB Driver

 

After that, OpenHantek runs fine in Windows. Actually, this process is not very complicated, the reverse is more complicated. When I previously tried to uninstall WinUSB drivers using zadig, I couldn't get HOS to run again. I had to uninstall Hantek drivers and reinstall them. If Hantek driver is installed and still actively used, the error message "UploadFirmware: Couldn't open device..." appears when running OpenHantek. Finally, the only way to run both programs on the same machine is to install a linux VM in Windows. Even though the opposite is also possible, it's not practical since OpenHantek can be installed in a live linux system (a VM without a disk) with a single command (Fedora or Mint).

What about other alternatives? I didn't know about other alternatives before. While doing some research for this article, I came across the article about six different oscilloscope software for Hantek. OpenHantek stands out in this comparison. I only had heard of Open6022 from these, but when I had downloaded it, it was a single zip file consisting of some messy C++ code and without any single compilation guide or documentation. I believe it is improved over time. From its screenshots for example, the inforation in "measure" window is listed side by side for each channel as I suggested for HOS. These software may also be worth a try.