17

Is there a way to write a script that will copy files from an ADB shell using run-as?

The only way I know of to copy in the adb shell is using cat source > dest (edit: modern android versions have the cp command, which makes this question unnecessary), but I am only able to quote the greater-than sign one level deep - so my script can pass it to adb shell, but not to adb shell run-as.

For example, this works:

adb shell "cat source > dest"

But this does not:

adb shell run-as "cat source > dest"

Nor this:

adb shell "run-as cat source \> dest"

I even tried created a small script and uploading it to the device, but I can't seem to run the script from the adb shell - it tells me "permission denied". I can't chmod the script, either.

The reason I want to do this is to copy a file into an app's private storage area - specifically, I am using a script to modify shared preferences and put the modified preferences back. Only the app itself or root can write to the file I want, however.

The use case in this scenario is coping a file to a protected location on the device, not retrieving it; for retrieving, there are already good answers in this question.

13
  • Likely you cannot execute the uploaded script as the external storage is mounted with the noexec flag. If you put it in the app's private storage or some tmp directory under /data you should be able to make it executable. However, what about building export functionality into the (debug version of) the app, to do the copy that way? Another option is to pipe commands from something like echo into an adb shell run-as com.foo.bar session. Mar 28, 2014 at 2:55
  • Is it possible to use adb to copy a script to the app's private storage, though? I always get permission denied, and if I could push to the app's private storage then I would not have this problem. I will try to pipe from echo, however. Mar 28, 2014 at 8:07
  • You could have the app write out the script or give you write access to a directory. Mar 28, 2014 at 11:53
  • 1
    Incidentally, Android now has a cp command if you are on a recent version. Mar 28, 2014 at 11:55
  • I was looking for something without help from the app because I have a lot of sandboxes that I regularly sync with version control and there are a lot of people working on the project; although I can make ad-hoc changes to the app, they likely to last only until the next time I forget about the changes when doing a revert. That is why it would be nice to have the script do everything. Unfortunately I don't have a recent enough version on the phone I'm using to have cp. Thank you for your help, by the way! Mar 28, 2014 at 18:39

4 Answers 4

23

The OP tried to combine the following 3 commands (that he had no problem executing one after another in the interactive shell session) into a single non-interactive command:

adb shell
run-as com.example.app
cat /sdcard/temp_prefs.xml > shared_prefs/com.example.app_preferences.xml

For simplicity let's start from within an interactive adb shell session. If we just try to combine the last two commands into a single line:

run-as com.example.app cat /sdcard/temp_prefs.xml > shared_prefs/com.example.app_preferences.xml

This would not work because of how shell redirection works - only the cat /sdcard/temp_prefs.xml part of the command would be run with com.example.app UID

Many people "know" to put the part of the command around redirection into quotes:

run-as com.example.app "cat /sdcard/temp_prefs.xml > shared_prefs/com.example.app_preferences.xml"

This does not work because the run-as command is not smart enough to parse the whole command. It expects an executable as the next parameter. The proper way to do it would be to use sh instead:

run-as com.example.app sh -c "cat /sdcard/temp_prefs.xml > shared_prefs/com.example.app_preferences.xml"

So can we just prepend adb shell to the command and be done with it? Not necessarily. By running the command from your PC you also add another local shell and its parser. Specific escape requirements would depend on your OS. In Linux or OSX (if your command does not already contain any ') it is easy to single-quote the whole command like so:

adb shell 'run-as com.example.app sh -c "cat /sdcard/temp_prefs.xml > shared_prefs/com.example.app_preferences.xml"'

But sometimes it is just easier to use an alternative solutions with (-out or less) quotes:

adb shell run-as com.example.app cp /sdcard/temp_prefs.xml shared_prefs/com.example.app_preferences.xml

Or if your device does not have the cp command:

adb shell run-as com.example.app dd if=/sdcard/temp_prefs.xml of=shared_prefs/com.example.app_preferences.xml

Also notice how I used shared_prefs/com.example.app_preferences.xml instead of full /data/data/com.example.app/shared_prefs/com.example.app_preferences.xml - normally inside of run-as command your current directory is the HOME dir of your package.

1
  • I assume this only works if you have only 1 profile on the device, as it will run as User 0, meaning this will not work for work/enterprise/secondary profiles.
    – Chapz
    Dec 15, 2022 at 15:07
13

Following Chris Stratton's advice, the way I eventually got this to work was as follows (for copying shared preferences back to the device):

adb push shared_prefs.xml /sdcard/temp_prefs.xml
cat <<EOF | adb shell
run-as com.example.app
cat /sdcard/temp_prefs.xml > /data/data/com.example.app/shared_prefs/com.example.app_preferences.xml
exit
exit
EOF

Piping directly to adb shell run-as did not work, and I do not know why, but piping to adb shell does. The trick is to then call run-as from the interactive shell, and it continues to accept input from the pipe.

The HERE doc lets me easily embed the newlines to separate commands and in general just makes it readable; I did not have much luck with semicolons, but that might have been because of the way I was doing things. I believe it might work with other methods of piping multiple commands/newlines; I stopped the experiment once I finally got it to work.

The two exits are necessary to prevent a hanging shell (killable with CTRL-C); one for run-as, and the other for adb shell itself. Adb's shell doesn't respond to end-of-file very nicely, it seems.

2
  • Nice job figuring out how to make it work. My mistake was trying to pipe into adb shell run-as which apparently does behave differently. Mar 31, 2014 at 1:00
  • @ChrisStratton I tried it that way too at first. It was only after I figured out that it went through to adb shell fine that I thought of doing it this way. Mar 31, 2014 at 2:11
3

you could just change the permission of the directory and then pull all the files out. but for me i was looking for just one shared preference file and i was able to get the data like this:

PACKAGE='com.mypackage.cool'
SHAREDPREF_FILE="${PACKAGE}_preferences.xml"

    adb shell "run-as $PACKAGE cat /data/data/$PACKAGE/shared_prefs/$SHAREDPREF_FILE">$SHAREDPREF_FILE

now we have the data of the sharedpreference file stored in a file of the same name.

0

Using the latest adb (ADB v1.0.41 / Version 33.0.3) and a Play Store emulator image I experienced adb root not being granted. I also could not copy from /data/local/ or /storage/emulated/0/ due to not having permissions when run-as com.myapp.app

new_prefs_path="my_machine.xml"
config="$(cat $new_prefs_path)"
my_app_uri="com.myapp.app"
adb shell "run-as $my_app_uri sh -c 'echo \"$config\" > shared_prefs/on_android.xml'"

This fixes it for me as a bash script. It's made slightly more complicated by needing to be configurable for different apps and complex payloads. We take a file (could be generated earlier in this script) and read it to a variable. We then start shell, do run-as my app and run echo expanding the read file to a file in shared_prefs.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.