2

I am trying to write a BASH script that will issue commands to ADB to remove packages from a list of packages in a file. However, my script leaves the loops that iterates over the file if the script executes any adb command. While ridiculous looking, I added some "Made it here" echo printouts to figure out where things were terminating in my confusion. The script execution seems to be at the end of the script, it just stops looping before the file's end for reasons I don't understand.

My current script with these "hacky" debug prints looks like this:

#!/bin/bash

if [[ -z $1 ]]
then
    echo "Enter file name for list of packages to purge"
    read PurgeFile
else
    PurgeFile=$1
fi


LINE=1

while read -r CURRENT_LINE
    do
        if [[ $CURRENT_LINE != "" ]]
        then
            echo "attempting to remove: $LINE: $CURRENT_LINE"
            #note, Next line causes the loop to exit if not commented out
            adb shell pm uninstall --user 0 $CURRENT_LINE || true
        fi
        echo "Made it here"
    ((LINE++))
echo "Made it to outer loop"
done < $PurgeFile

If the line that starts adb shell pm ... is commented out, this script correctly goes through the entire file and prints the "Made it ..." echo-printouts interspersing them correctly. But if that adb command is left in, the script makes it to the first non-empty line in the file and seems to get to the done < $PurgeFile before ending.

This is true no matter what the 'adb' command's output is as I have given it setups where it both succeeds and fails.

Just for reference:

here is the printout from a run where the first adb call is a success, where the rest of the list in the file should also be successful:

$ bash Android_Purge_Script.sh  Purge_Packages.txt
attempting to remove: 1: android.autoinstalls.config.samsung
Success
Made it here
Made it to outer loop

here is the pintout from a run where the first 'adb' call is a failure, but where the rest of the list in the file should be successful:

bash Android_Purge_Script.sh  Purge_Packages.txt
attempting to remove: 1: android.autoinstalls.config.samsung
Failure [not installed for 0]
Made it here
Made it to outer loop

And below is the first few dozen lines of the printout when using the exact same Purge_Packages.txt file if I comment out the adb call in the script. The output is abridged so as to not waste everyone's time with a couple of hundred lines demonstrating the same pattern again with minor variation in instances over and over.

attempting to remove: 1: android.autoinstalls.config.samsung
Made it here
Made it to outer loop
attempting to remove: 2: com.amazon.appmanager
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 4: com.android.apps.tag
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 6: com.android.bips
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 8: com.android.bluetoothmidiservice
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 10: com.android.bookmarkprovider
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 12: com.android.calllogbackup
Made it here
Made it to outer loop
Made it here
Made it to outer loop
attempting to remove: 14: com.android.chrome
<etc....>

One can see the mostly alternating empty lines in the file correctly iterated over in the above example wherever the four "Made it..." echo-prints occur before the next line with content.

EDIT: Based on the answers, this is apparently a recurring issue with lots Bash scripts when using command line commands that accepts streams. Many of the prior questions that have been answered are linked below, but while the general issue and solution was the same, this question has independent value because none of those particular implementations of the solution worked here (basically to patch in a stream like /dev/null) and instead a different approach was taken here for the implementation that ultimately worked (adjust the command to one that does not take iostream as input.)

3

4 Answers 4

2

adb is reading input. Don't let it.

while read -r CURRENT_LINE; do
   ...
   # close input
   adb shell pm uninstall --user 0 $CURRENT_LINE <&-
done < $PurgeFile

See https://mywiki.wooledge.org/BashFAQ/001#How_to_keep_other_commands_from_.22eating.22_the_input .

Sign up to request clarification or add additional context in comments.

4 Comments

The <&- is creating a new error. If that is there I am getting "ADB server didn't ACK" errors with adb but the usual solutions, to reboot, restart, reconnect, ADB aren't working, because that isn't the issue. Without the <&- it goes right back to the old behavior which is correct for one line and means it isn't a connection issue, but with the <&- it iterates through the whole thing throwing connection errors.
I think you have the right answer among those given, but the specific <&- isn't working for me. I tried to read the link you sent me to, and I get the concept but I am having trouble figuring out what another specific implementation might look like.
Try </dev/null or </dev/zero. Maybe adb doesn't like when stdin is closed.
Thanks. The fix I got, posted as the bottom answer now, was to add the -n flag to the adb shell which tells it not to use iostream for input.
1

I have been using the following one liner to uninstall packages selectively from my termianl:

for pkg in $(adb shell cmd package list packages | awk -F: '{print $2}' | grep '^com\.example\.') ; do echo "Uninstall $pkg ? : (Y/n)" ; read -n1 ; echo ; [[ $REPLY =~ (Y|y) ]] && { echo "Uninstalling $pkg" ; adb uninstall "$pkg" ; } || echo "Keeping $pkg" ; echo ; echo ; done

To fix yours, you just need to replace the line:

adb shell pm uninstall --user 0 $CURRENT_LINE || true

with:

adb uninstall "$CURRENT_LINE"

This does the job. Example output:


attempting to remove: 1: com.example.scaffold_color_conditionally_change
Failure [DELETE_FAILED_INTERNAL_ERROR]
Made it here
Made it to outer loop
attempting to remove: 2: com.example.fix_render_physical_model_given_infinite_size
Success
Made it here
Made it to outer loop

Note: The failure to remove a package is there because the package was already removed.

1 Comment

I believe you but it isn't working for me. It isn't finding the packages without the --user 0 it just says /system/bin/sh: uninstall: inaccessible or not found and it is also failing to iterate exactly like before.
1

Fixed it.

@KamilCuk was correct about the issue and the general solution in their answer here, but their specific implementation didn't work for me as it created other errors (see my reply to KamilCuk's answer if curious)

As KamilCuk correctly IDed, the adb command was accepting input continuously as a stream and was just eating the whole file as a single input while it was being looped through line by line.

The answer I got to work was to set the -n flag in the adb shell command which is "Don't read from stdin" in the man page. So the fix was going from the default streaming version of:

adb shell pm uninstall --user 0 "$CURRENT_LINE"  

to the "Don't read from stdin" version of:

adb shell -n pm uninstall --user 0 "$CURRENT_LINE"

5 Comments

In most modern versions of ADB, adb shell <COMMAND> already behaves as a non-interactive shell, making the -n flag redundant for most use cases.
Well not here, because doing that literally fixed the problem. Doing that was the correct solution.
This is similar to the problem when running ssh in a while read loop. And ssh has the same -n option.
@Barmar interesting. I wish I had known about that or that someone had told me about that earlier, because it clearly is the same thing, but no one explained that known fix to me until after I had to come up with it from scratch by trial and error. I do appreciate you bringing it to my attention though, it helps reassure me that my solution wasn't just a kluge. Indeed, even after I got that working I still had people telling me here that wouldn't work, which is baffling as I only reported it after I got that working.
I surprises everyone who encounters it for the first time. You don't think that these programs read from stdin because they don't wait for you to enter anything when they're used interactively.
1

Bash as a built-in debugger! Starts your script like that

bash -x yourscript.sh.

It will help to locate the line and variable value that fails, as a start. Another approach is to rewrite your script and chain all commands with &&, so your program will actually stops where a quirk happen. Not easy using loops like in your script, this said.

Another way is to use trap.

Because bash, unlike most languages, does not stop on failures! The rest of the program runs to the end, debugging is hard.

1 Comment

"t will help to locate the line and variable value that fails, as a start. " From what I can tell it is exiting successfully. But then, this is only the fourth Bash script I have ever written, with the first being "Hello World" and the other two slightly more advanced, so I might be under a misconception here.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.