A simple typo in an Android localization variable (for instance %1d instead of %1$d in strings.xml) can lead to a crash.
We can't afford to test exhaustively (many screens, some very rarely shown, tens of languages, very frequent releases, no revenue) so we must find a smarter way. Those errors are not shown in Eclipse, and actually I am looking for a non-visual tool so that it can be called by our automatic release tool.
I wrote the following script to check the localisation files:
#! /bin/sh
# Spot malformed string replacement patterns in Android localization files.
grep -R "%1$ s" values*
grep -R "%1$ d" values*
grep -R '%' values* |
sed -e 's/%/\n%/g' | # Split lines that contain several expressions
grep '%' | # Filter out lines that do not contain expressions
grep -v ' % ' | # Lone % character, not a variable
grep -v '%<' | # Same, at the end of the string
grep -v '% ' | # Same, at the beginning of the string
grep -v '%で' | # Same, no spaces in Japanese
grep -v '%s' | # Single string variable
grep -v '%d' | # Single decimal variable
grep -v '%[0-9][0-9]\?$s' | # Multiple string variable
grep -v '%[0-9][0-9]\?$d' | # Multiple decimal variable
grep -v '%1$.1f' | # ?
grep -v '%.1f'
grep -R '%' values*
PROBLEM: It fails to catch problems in Arabic localization files.
QUESTION: rather than re-inventing the wheel, is there already such a validation tool? (not Eclipse)
If not: what checks did I forget?
Note: this script is open source
This sounds like something that should be incorporated into the Lint tool in ADT.
Related
I am working on Crowdin client js api to fetch translated strings from Crowdin. I have implemented upload of strings from res dir to crowdin, download of translated strings from Crowdin and update of localized strings to android res dir.
But the issues I am trying to fix is that how the strings format errors can be found and fixed? There are shell script to do that but it does work on plurals and some more strings.
if grep -RHn "%1$ s" res/values*; then
echo "Found '%1$ s'-related error"
EXIT_STATUS=$((EXIT_STATUS + 1));
fi
if grep -RHn "%1$ d" res/values*; then
echo "Found '%1$ s'-related error"
EXIT_STATUS=$((EXIT_STATUS + 1));
fi
if grep -RHn "%1" res/values* | grep -v "%1\\$"; then
echo "Found '%1'-related error"
EXIT_STATUS=$((EXIT_STATUS + 1));
fi
if grep -RHn '%' res/values* |
sed -e 's/%/\n%/g' | # Split lines that contain several expressions
grep '%' | # Filter out lines that do not contain expressions
grep -v ' n% ' | # Lone % character, not a variable
grep -v '(n%)' | # Lone % character, not a variable
grep -v 'n%<' | # Same, at the end of the string
grep -v '>n% ' | # Same, at the beginning of the string
grep -v '%で' | # Same, no spaces in Japanese
grep -v '%s' | # Single string variable
grep -v '%d' | # Single decimal variable
grep -v '%[0-9][0-9]\?$s' | # Multiple string variable
grep -v '%[0-9][0-9]\?$d' | # Multiple decimal variable
grep -v '%1$.1f' | # ?
grep -v '%.1f' |
grep -v '%\\n' |
then
echo "Found grep errors but if you are not on macOS they are likely false positive. Ignoring"
#EXIT_STATUS=$((EXIT_STATUS + 1))
fi
Can gradle lint be used to find errors for format in localized strings?
I'm developing an application that uses ADB Shell to interface with android devices, and I need some way of printing out the application name or label of an application, given maybe their package name.
In short, I need a way of getting app names (i.e. "Angry Birds v1.0.0") for user installed applications through adb shell.
Any light on the matter? Any help is appreciated on this.
adb shell pm list packages will give you a list of all installed package names.
You can then use dumpsys | grep -A18 "Package \[my.package\]" to grab the package information such as version identifiers etc
just enter the following command on command prompt after launching the app:
adb shell dumpsys window windows | find "mCurrentFocus"
if executing the command on linux terminal replace find by grep
If you know the app id of the package (like org.mozilla.firefox), it is easy.
First to get the path of actual package file of the appId,
$ adb shell pm list packages -f com.google.android.apps.inbox
package:/data/app/com.google.android.apps.inbox-1/base.apk=com.google.android.apps.inbox
Now you can do some grep|sed magic to extract the path : /data/app/com.google.android.apps.inbox-1/base.apk
After that aapt tool comes in handy :
$ adb shell aapt dump badging /data/app/com.google.android.apps.inbox-1/base.apk
...
application-label:'Inbox'
application-label-hi:'Inbox'
application-label-ru:'Inbox'
...
Again some grep magic to get the Label.
A shell script to accomplish this:
#!/bin/bash
# Remove whitespace
function remWS {
if [ -z "${1}" ]; then
cat | tr -d '[:space:]'
else
echo "${1}" | tr -d '[:space:]'
fi
}
for pkg in $(adb shell pm list packages -3 | cut -d':' -f2); do
apk_loc="$(adb shell pm path $(remWS $pkg) | cut -d':' -f2 | remWS)"
apk_name="$(adb shell aapt dump badging $apk_loc | pcregrep -o1 $'application-label:\'(.+)\'' | remWS)"
apk_info="$(adb shell aapt dump badging $apk_loc | pcregrep -o1 '\b(package: .+)')"
echo "$apk_name v$(echo $apk_info | pcregrep -io1 -e $'\\bversionName=\'(.+?)\'')"
done
Inorder to find an app's name (application label), you need to do the following:
(as shown in other answers)
Find the APK path of the app whose name you want to find.
Using aapt command, find the app label.
But devices don't ship with the aapt binary out-of-the-box.
So you will need to install it first. You can download it from here:
https://github.com/Calsign/APDE/tree/master/APDE/src/main/assets/aapt-binaries
Check this guide for complete steps:
How to find an app name using package name through ADB Android?
(Disclaimer: I am the author of that blog post)
This is what I just came up with. It gives a few errors but works well enough for my needs, matching package names to labels.
It pulls copies of all packages into subdirectories of $PWD, so keep that in mind if storage is a concern.
#!/bin/bash
TOOLS=~/Downloads/adt-bundle-linux-x86_64-20130717/sdk/build-tools/19.1.0
AAPT=$TOOLS/aapt
PMLIST=adb_shell_pm_list_packages_-f.txt
TEMP=$(echo $(adb shell mktemp -d -p /data/local/tmp) | sed 's/\r//')
mkdir -p packages
[ -f $PMLIST ] || eval $(echo $(basename $PMLIST) | tr '_' ' ') > $PMLIST
while read line; do
package=${line##*:}
apk=${package%%=*}
name=${package#*=}
copy=packages$apk
mkdir -p $(dirname $copy)
if [ ! -s $copy ]; then # copy it because `adb pull` doesn't see /mnt/expand/
adb shell cp -f $apk $TEMP/copy.apk
adb pull $TEMP/copy.apk $copy
fi
label=$($AAPT dump badging $copy || echo ERROR in $copy >&2 | \
sed -n 's/^application-label:\(.\)\(.*\)\1$/\2/p')
echo $name:$label
done < <(sed 's/\r//' $PMLIST)
adb shell rm -rf $TEMP
So I extremely grateful to jcomeau_ictx for providing the info on how to extract application-label info from apk and the idea to pull apk from phone directly!
However I had to make several alteration to script it self:
while read line; do done are breaking as a result of commands within while loop interacting with stdin/stdout and as a result while loop runs only once and then stops, as it is discussed in While loop stops reading after the first line in Bash - the comment from cmo I used solution provided and switched while loop to use unused file descriptor number 9.
All that the script really need is a package name and adb shell pm list packages -f is really excessive so I changed it to expect a file with packages list only and provided example on how one can get one from adb.
jcomeau_ictx script variant do not take in to account that some packages may have multiple apk associated with them which breaks the script.
And the least and last, I made every variable to start with underscore, it's just something that makes it easier to read script.
So here another variant of the same script:
#!/bin/bash
_TOOLS=/opt/android-sdk-update-manager/build-tools/29.0.3
_AAPT=${_TOOLS}/aapt
#adb shell pm list packages --user 0 | sed -e 's|^package:||' | sort >./packages_list.txt
_PMLIST=packages_list.txt
rm ./packages_list_with_names.txt
_TEMP=$(echo $(adb shell mktemp -d -p /data/local/tmp) | sed 's/\r//')
mkdir -p packages
[ -f ${_PMLIST} ] || eval $(echo $(basename ${_PMLIST}) | tr '_' ' ') > ${_PMLIST}
while read -u 9 _line; do
_package=${_line##*:}
_apkpath=$(adb shell pm path ${_package} | sed -e 's|^package:||' | head -n 1)
_apkfilename=$(basename "${_apkpath}")
adb shell cp -f ${_apkpath} ${_TEMP}/copy.apk
adb pull ${_TEMP}/copy.apk ./packages
_name=$(${_AAPT} dump badging ./packages/copy.apk | sed -n 's|^application-label:\(.\)\(.*\)\1$|\2|p' )
#'
echo "${_package} - ${_name}" >>./packages_list_with_names.txt
done 9< ${_PMLIST}
adb shell rm -rf $TEMP
I have two lists
list1:
A:1
B:3
C:1
D:5
list2:
1:blue
3:green
5:red
How can i do for have something like:
Desired output(file3):
A:blue
B:green
C:blue
D:red
And here is my unworking code ...
#!/system/bin/bash
list1=$(cat file1)
list2=$(cat file2)
for i in "$list1"; do
num_file1=$( echo $i | cut -d ":" -f 2)
string_file2=$(cat $list2 | grep "$num_file1" | cut -d ":" -f 2)
echo -e "$i" | sed "s/$num_file1/$string_file2/" > list3
done
I also tried
sed 's/"$num_file1"/"$string_file2"/' and many other but failed every times for what i want .. Where i am wrung with sed ??
Ps: its on android ... and few command are misted ...
Give this awk line a try:
awk -F':' 'NR==FNR{k[$1]=$2;next}{print $1 FS k[$1]}' f2 f1
I didn't test the code, but it should work.
Note this assumes that all idx in file1 we have corresponding entry in file2
The awk solution is perfectly fine. Using lesser commands, you could say:
sort -t \: -k 2 list1 | join -t \: -1 2 - list2 | cut -d \: -f 2,3 | sort -t \:
Where i am wrung with sed ??
You were quite close, wrong only in three places.
for i in "$list1"; do has to be for i in $list1; do without quotes, since you want to process the lines individually, not the whole $list1 at once.
string_file2=$(cat $list2 | …) has to be string_file2=$(echo "$list2" | …) with echo instead of cat (since you already read the file2 into the variable $list2) and with quotes to preserve the line separation.
The output redirection > list3 has to be moved away from the echo -e "$i" | sed … line to the end of the done line, otherwise only the last output line would remain in file3.
Another approach to the problem would be to use an array for the colors, indexed by their number:
#!/bin/bash
eval rgb=($(sed 's/\(.*\):/[\1]=/' <file2)) # change 1:blue to [1]=blue etc.
while IFS=: read letter number
do echo $letter:${rgb[$number]} # change A:1 to A:blue etc.
done <file1 >file3
I am wanting to extract out the package name of my apk for a script.
I can list the package name with something like this
./aapt dump badging <apk.path> | grep package
The output looks like this
package: name='com.example.app' versionCode='' versionName='4.6.10' platformBuildVersionName='6.0-2166767'
so I want to run the aapt command and it only return com.example.app
I figured it would be something like, but still returns everything.
./aapt dump badging <apk.path> | egrep package:\ name='(.*?)'
Use grep again:
grep -Po "(?<=name=')[^']*" file
This uses Perl regex to print only what comes after name=' and up to the following '.
Test
$ cat a
package: name='com.example.app' versionCode='' versionName='4.6.10' platformBuildVersionName='6.0-2166767'
buuu
$ grep -Po "(?<=name=')[^']*" a
com.example.app
Got it working by doing multiple greps and some regex. Below worked from me. Not sure if its the best way, but works.
$ aapt dump badging <path.to.apk> | grep package | grep -Eo '[a-z]+\.\w+\.\w+'
$ com.example.app
I'm using aapt tool to read content of apk file with:
aapt d badging myapk.apk
But the output is too much. I just need the package name and version name. Any way to limit it?
Update: I got it to work on window cmd. Look like this:
aapt d badging myapk.apk | find "pack"
//try this
aapt d badging myapk.apk | grep 'pack'
its showing for me as
padmakumar#padmakumar-desktop:~$ aapt d badging ./Desktop/NhpAndroid_tablet_k4.apk | grep 'pack'
package: name='com.ti.et.nspire.android' versionCode='1' versionName='1.0'
Here is a trick that work like charm for me.
I'm using Backtrack 5 r2 ; GNU bash, version 4.1.5(1)-release (i486-pc-linux-gnu)
Assuming that "./aapt" executable is on the same directory of the Shell or Script. If not just add the path to executable or use export aapt="/path/to/aapt" and use the variable path.
out=$(./aapt dump badging GameCIH.apk | grep 'application-label:' | awk -F: 'match($0,":"){ print substr($0,RSTART+1)}' | tr -d "'" )
From aplication-label:'GameCIH' on apk
To only:
GameCIH
One last thing. If you want the package name or version name then do this:
out=$(./aapt dump badging GameCIH.apk | grep 'versionName=' | awk -F: 'match($0,"versionName="){ print substr($2,RSTART-8)}' | tr -d "'" )
This will return for example:
versionName=3.0.0
Just change the values versionName to whatever you need.
Change RSTART-8 for something like this: RSTART+4 and that will return:
3.0.0
I hope this could help!
aapt d badging myapk.apk | grep package
for Windows, download UnxUtils to get grep and much more Linux command:
http://unxutils.sourceforge.net/UnxUtils.zip
aapt d badging myapk.apk | awk '/package/ {print($2)}' | awk '{print(mstr[split($1, mstr, \"=\")])}' | tr -d \"'\"
Here's the Bash function:
# Display package name and version of APK file(s)
apk(){
(
set -o pipefail
for path in "$#"; do
aapt dump badging "$path" \
| awk $'
BEGIN {
p=""
v=""
}
match($0, /^package: name=\'([^\']*)\'/, a) {
p=a[1]
}
match($0, /versionName=\'([^\']*)\'/, b) {
v=b[1]
}
END {
if (length(p) && length(v)) {
print p, v
}
}'
done
)
}