您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“iOS如何通過shell腳本批量修改屬性”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“iOS如何通過shell腳本批量修改屬性”這篇文章吧。
下面是執行腳本替換了屬性的結果圖,腳本把所有需要替換的屬性添加了abc后綴,當然依然是可以正常編譯運行的
源碼:https://gitee.com/dhar/YTTInjectedContentKit
分析
原理分析
objc代碼中的類名、屬性、方法、源文件路徑等信息最終會被打包到二進制文件中,保存在二進制文件中的.sym符號表段中,可以使用objdump -t命令查看二進制符號信息,以下的命令把objdump -t的結果寫入到文件InjectedContentKit_Example_Symbols中去。
objdump -t InjectedContentKit_Example > InjectedContentKit_Example_Symbols
文件的內容會很大,所以選擇了幾個代表性的內容說明:
0000000100026350 l d __TEXT,__text __text # 這里保存的是類源文件的路徑符號信息 0000000000000000 l d *UND* /Users/aron/PuTaoWorkSpace/project/sscatch/DevPods/InjectedContentKit/InjectedContentKit/Classes/Composer/PubSearchDataComposer.h # 這里保存的是屬性對應的var信息 0000000000000000 l d *UND* _OBJC_IVAR_$_TextCardItem._title 0000000000000000 l d *UND* _OBJC_IVAR_$_TextCardItem._showReact 0000000000000000 l d *UND* _OBJC_IVAR_$_TextCardItem._topChart 0000000000000000 l d *UND* _OBJC_IVAR_$_TextCardItem._reaction # 這里保存的是屬性信息對應的getter方法信息 00000001000264a0 l F __TEXT,__text -[TextCardItem title] 00000001000264c0 l F __TEXT,__text -[TextCardItem showReact] 00000001000264f0 l F __TEXT,__text -[TextCardItem topChart] 0000000100026510 l F __TEXT,__text -[TextCardItem setTopChart:] # 這里保存的是屬性信息對應的setter方法信息 00000001000028a0 l F __TEXT,__text -[SSCatchInviteScheduler setOrganizer:] 00000001000028e0 l F __TEXT,__text -[SSCatchInviteScheduler setInputCardBack:] 0000000100002920 l F __TEXT,__text -[SSCatchInviteScheduler setInputTextBack:] # 這里保存的是類文件的文件名信息 0000000000000000 l d *UND* PubSearchDataComposer.m 000000005a937587 l d __TEXT,__stub_helper __stub_helper 00000001000251c0 l d __TEXT,__text __text
從上面可以看出,二進制中保留了很多信息和源代碼有很大關系,我們做個簡單的猜測蘋果后臺機器審查二進制的時候會通過二進制中的符號進行對比,如果兩個二進制(一個主版本、一個殼版本)代碼中的符號重合度超過某個閾值,就會判定這是發布殼版本的行為,而這是蘋果說不允許的,所以可行的方法是修改源文件中的這些信息來繞過蘋果的審查機制。
另外猜測蘋果應該是不會根據代碼中的流程控制來判斷的,因為二進制中的控制流程已經是機器碼了,反編譯出來也就是匯編代碼,只要稍微做點改動二進制(.text段)就會變化很大。所以從這個方面來判斷就難度很大了。
步驟分析
主要有以下幾個步驟
尋找到需要替換的源文件中的所有的屬性,處理之后保存在配置文件中
用戶自定義一個黑名單配置文件
某部分需要隔離的代碼中的屬性生成黑名單配置文件
把需要替換的源文件中的所有匹配的屬性做批量的替換
這里說明下為什么第一步需要保存在配置文件中,因為第三步的操作有部分和第一步是相同的,所有這部分單獨出來一個模塊共用,都是輸入一個文件夾,最終保存在指定的文件中,后面的代碼中可以看到這部分。
實現
單步實現
1、尋找到需要替換的源文件中的所有的屬性,處理之后保存在配置文件中
這一步的功能是客戶端輸入一個需要處理的源碼文件夾,遞歸遍歷該源碼文件夾獲取所有源碼文件(.h .m 文件)。使用正則匹配找到屬性名稱,暫時保存到數組中,最后經過黑名單過濾、去重過濾、其他過濾條件過濾,最終把待處理的屬性保存到客戶端輸入的輸出文件中。
可以分解為一下幾個小步驟
遞歸遍歷文件夾獲取源碼文件
正則匹配源碼文件的屬性
過濾屬性(可選)
保存屬性到文件
這部分功能的源碼如下:
文件名: GetAndStoreProperties.sh
該腳本在多個地方都有用到,所以作為一個單獨的模塊,定義了一些參數,以適應不同的應用場景。在下面可以看到使用該腳本的地方。
#!/bin/bash ######################## # 腳本功能:從指定目錄獲取和保存屬性到指定的文件 # 輸入參數 -i 輸入的文件夾 # 輸入參數 -o 保存的文件 # 輸入參數 -f 使用黑名單和自定義過濾條件的參數 # 輸入參數 -c 自定義的黑名單文件 ######################## ####### 參數定義 param_input_dir="" param_output_file="" param_custom_filter_file="" param_should_use_filter=0 ####### 參數解析 while getopts :i:o:c:f opt do case "$opt" in i) param_input_dir=$OPTARG echo "Found the -i option, with parameter value $OPTARG" ;; o) param_output_file=$OPTARG echo "Found the -o option, with parameter value $OPTARG" ;; c) param_custom_filter_file=$OPTARG echo "Found the -c option, with parameter value $OPTARG" ;; f) echo "Found the -f option" param_should_use_filter=1 ;; *) echo "Unknown option: $opt";; esac done ####### 配置 # 屬性黑名單配置文件 blacklist_cfg_file="$(pwd)/DefaultBlackListPropertiesConfig.cfg" ####### 數據定義 # 定義保存源文件的數組 declare -a implement_source_file_array implement_source_file_count=0 # 定義保存屬性的數組 declare -a tmp_props_array props_count=0 # mark: p384 # 遞歸函數讀取目錄下的所有.m文件 function read_source_file_recursively { echo "read_implement_file_recursively" if [[ -d $1 ]]; then for item in $(ls $1); do itemPath="$1/${item}" if [[ -d $itemPath ]]; then # 目錄 echo "處理目錄 ${itemPath}" read_source_file_recursively $itemPath echo "處理目錄結束=====" else # 文件 echo "處理文件 ${itemPath}" if [[ $(expr "$item" : '.*\.m') -gt 0 ]] || [[ $(expr "$item" : '.*\.h') -gt 0 ]]; then echo ">>>>>>>>>>>>mmmmmmm" implement_source_file_array[$implement_source_file_count]=${itemPath} implement_source_file_count=$[ implement_source_file_count + 1 ]; fi echo "" fi done else echo "err:不是一個目錄" fi } # 讀取源碼中的屬性,保存到數組中 # 參數一: 源碼文件路徑 function get_properties_from_source_file { local class_file=$1; echo "class_file=${class_file}" properties=$(grep "@property.*" ${class_file}) IFS_OLD=$IFS IFS=$'\n' for prop_line in $properties; do echo ">>>>>${prop_line}" asterisk_seperator_pattern="\*" if [[ ${prop_line} =~ ${asterisk_seperator_pattern} ]]; then # 從左向右截取最后一個string后的字符串 prop_name=${prop_line##*${asterisk_seperator_pattern}} # 從左向右截取第一個string后的字符串 seal_pattern=";*" seal_pattern_replacement="" prop_name=${prop_name//${seal_pattern}/${seal_pattern_replacement}} subsring_pattern="[ |;]" replacement="" prop_name=${prop_name//${subsring_pattern}/${replacement}} if [[ ${param_should_use_filter} -gt 0 ]]; then grep_result=$(grep ${prop_name} ${blacklist_cfg_file}) echo "grep_result = >>${grep_result}<<" custom_grep_result="" if [[ -n ${param_custom_filter_file} ]]; then custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file}) fi if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then echo "--${prop_name}--存在配置文件中" else echo "--${prop_name}--XXX不存在配置文件中" tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] echo ">>>>>>>result_prop_name=${prop_name}" fi else tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] fi fi done IFS=$IFS_OLD } # 獲取目錄下的所有源文件,讀取其中的屬性 function get_properties_from_source_dir { local l_classed_folder=$1 echo "獲取需要處理的源文件... ${l_classed_folder}" # 讀取需要處理目標文件 read_source_file_recursively ${l_classed_folder} echo "讀取源文件中的屬性..." for(( i=0;i<${#implement_source_file_array[@]};i++)) do class_file=${implement_source_file_array[i]}; echo "處理源文件:${class_file}" get_properties_from_source_file ${class_file} done; } # 把獲取到的屬性過濾之后寫入文件中 # 過濾步驟包含去重、去掉簡單詞匯、去掉長度少于多少的詞匯 # 如果在執行的過程中遇到特殊情況,添加到黑名單配置(DefaultBlackListPropertiesConfig.cfg文件中添加配置) function post_get_properties_handle { local prop_config_file=$1 # 寫入文件中 echo "# Properties Configs" > ${prop_config_file} for key in $(echo ${!tmp_props_array[*]}) do # echo "$key : ${tmp_props_array[$key]}" echo ${tmp_props_array[$key]} >> ${prop_config_file} done # 去重 cfg_back_file="${prop_config_file}.bak" mv ${prop_config_file} ${cfg_back_file} sort ${cfg_back_file} | uniq > ${prop_config_file} # 過濾 if [[ ${param_should_use_filter} -gt 0 ]]; then mv ${prop_config_file} ${cfg_back_file} echo "# Properties Configs Filtered" > ${prop_config_file} IFS_OLD=$IFS IFS=$'\n' # 上一行的內容 lastLine=""; for line in $(cat ${cfg_back_file} | sed 's/^[ \t]*//g') do if [[ ${#line} -le 6 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then # 長度小于等于6或者注釋內容的行不處理 echo "less then 6 char line or comment line" else if [[ -n ${lastLine} ]]; then # 上一行是非空白行 # 比較上一行內容是否是當前行的一部分,不是添加上一行 if [[ ${line} =~ ${lastLine} ]]; then echo "${line} 和 ${lastLine} 有交集" else echo ${lastLine} >> ${prop_config_file} fi fi # 更新上一行 lastLine=${line} fi done IFS=${IFS_OLD} fi # 刪除臨時文件 rm -f ${cfg_back_file} } get_properties_from_source_dir ${param_input_dir} post_get_properties_handle ${param_output_file}
使用以上腳本生成的配置文件 PropertiesConfigs.cfg 部分如下:
# Properties Configs Filtered UserRestrictionLabel aboutusButton activitySamplers addAddressPress addressSamplers addressTextBox appealPress appliedGroupedSamplers appliedSamplers applyPress asyncArray asyncListSampler audioPlayer
2. 用戶自定義一個黑名單配置文件
在實踐的過程中,替換屬性的符號有時候會把系統類的屬性替換了,比如
把 AppDelegate 中的 window 屬性替換了,導致了編譯鏈接沒錯,但是界面出不來了,因為初始的window對象找不到了
把 UIButton 中的 titleLabel 屬性替換了,直接導致了編譯出錯
對于這類問題,需要在黑名單中配置一些默認的過濾屬性,對于黑名單中的這些屬性不處理即可,在我的業務場景下,黑名單文件的配置如下:
文件名:DefaultBlackListPropertiesConfig.cfg
# BlackListPropertiesConfig.cfg # 屬性黑名單配置,在此配置文件中的屬性不需要替換名稱 window name title titleLabel layout appealSamplers
在 GetAndStoreProperties.sh 腳本使用到的代碼片段如下,其實就是使用了 grep 命來查找,判斷時候有找到,如果有就不處理,具體的可以看上面提供的完整的 GetAndStoreProperties.sh 腳本代碼
if [[ ${param_should_use_filter} -gt 0 ]]; then grep_result=$(grep ${prop_name} ${blacklist_cfg_file}) echo "grep_result = >>${grep_result}<<" custom_grep_result="" if [[ -n ${param_custom_filter_file} ]]; then custom_grep_result=$(grep ${prop_name} ${param_custom_filter_file}) fi if [[ -n ${grep_result} ]] || [[ -n ${custom_grep_result} ]]; then echo "--${prop_name}--存在配置文件中" else echo "--${prop_name}--XXX不存在配置文件中" tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] echo ">>>>>>>result_prop_name=${prop_name}" fi else tmp_props_array[$props_count]=$prop_name props_count=$[ props_count + 1 ] fi
3. 某部分需要隔離的代碼中的屬性生成黑名單配置文件
這部分的功能其實就是調用 GetAndStoreProperties.sh 這個腳本,最終把文件輸出的文件以追加的方式寫入到用戶自定義的黑名單屬性文件中。
#... # 黑名單類目錄 declare -a custom_blacklist_search_dirs custom_blacklist_search_dirs=("/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/SSCatchAPI" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Categories" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Components" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/External" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/HandyTools" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Macros" ) # ... # 屬性黑名單配置文件 custom_blacklist_cfg_file="$(pwd)/CustomBlackListPropertiesConfig.cfg" # ... # 獲取自定義的黑名單屬性并保存到文件中 echo "" > ${custom_blacklist_cfg_file} for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]} ./GetAndStoreProperties.sh \ -i ${custom_blacklist_search_dir}\ -o ${custom_blacklist_cfg_tmp_file} cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file} done #...
最終生成的用戶自定義的黑名單文件部分如下
文件:CustomBlackListPropertiesConfig.cfg
# Properties Configs DBFilePath ValidityString accessQueue age attributedNameString avatarURLString avatarUrlString backColorString bodyScheduler bodyView catchDateString cellHeight channelKey cityName conditionString # ....
4. 把需要替換的源文件中的所有匹配的屬性做批量的替換
這一步在前面三部的基礎上,查找并替換源碼目錄中在 PropertiesConfigs.cfg 配置文件中出現的屬性和屬性的引用,查找使用grep命令、替換使用了sed命令。腳本代碼如下
#!/bin/bash # 屬性重命名腳本 ####### 配置 # classes類目錄 classes_dir="$(pwd)/../InjectedContentKitx" # 黑名單類目錄 declare -a custom_blacklist_search_dirs custom_blacklist_search_dirs=("/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/SSCatchAPI" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Categories" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Components" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/External" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/HandyTools" "/Users/aron/PuTaoWorkSpace/project/sscatch/sscatch/Classes/Macros" ) # 配置文件 cfg_file="$(pwd)/PropertiesConfigs.cfg" # 屬性黑名單配置文件 blacklist_cfg_file="$(pwd)/DefaultBlackListPropertiesConfig.cfg" # 屬性黑名單配置文件 custom_blacklist_cfg_file="$(pwd)/CustomBlackListPropertiesConfig.cfg" custom_blacklist_cfg_tmp_file="$(pwd)/TmpCustomBlackListPropertiesConfig.cfg" # 屬性前綴,屬性前綴需要特殊處理 class_prefix="" # 屬性后綴 class_suffix="abc" # 檢測文件是否存在,不存在則創建 checkOrCreateFile() { file=$1 if [[ -f $file ]]; then echo "檢測到配置文件存在 $file" else echo "創建配置文件 $file" touch $file fi } # 配置文件檢查 checkOrCreateFile $cfg_file # 循環檢測輸入的文件夾 function checkInputDestDir { echo -n "請輸入需處理源碼目錄: " read path if [[ -d $path ]]; then classes_dir=$path else echo -n "輸入的目錄無效," checkInputDestDir fi } # 需處理源碼目錄檢查 if [[ -d $classes_dir ]]; then echo "需處理源碼目錄存在 $classes_dir" else echo "請確認需處理源碼目錄是否存在 $classes_dir" checkInputDestDir fi ####### 數據定義 # 定義屬性保存數組 declare -a rename_properties_config_content_array cfg_line_count=0 # 讀取屬性配置文件 function read_rename_properties_configs { IFS_OLD=$IFS IFS=$'\n' # 刪除文件行首的空白字符 https://www.jb51.net/article/57972.htm for line in $(cat $cfg_file | sed 's/^[ \t]*//g') do is_comment=$(expr "$line" : '^#.*') echo "line=${line} is_common=${is_comment}" if [[ ${#line} -eq 0 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then echo "blank line or comment line" else rename_properties_config_content_array[$cfg_line_count]=$line cfg_line_count=$[ $cfg_line_count + 1 ] # echo "line>>>>${line}" fi done IFS=${IFS_OLD} } function print_array { # 獲取數組 local newarray newarray=($(echo "$@")) for (( i = 0; i < ${#newarray[@]}; i++ )); do item=${newarray[$i]} echo "array item >>> ${item}" done } # 重命名所有的屬性 function rename_properties { # 讀取屬性配置文件 read_rename_properties_configs # print_array ${rename_properties_config_content_array[*]} # 執行替換操作 for (( i = 0; i < ${#rename_properties_config_content_array[@]}; i++ )); do original_prop_name=${rename_properties_config_content_array[i]}; result_prop_name="${class_prefix}${original_prop_name}${class_suffix}" sed -i '{ s/'"${original_prop_name}"'/'"${result_prop_name}"'/g }' `grep ${original_prop_name} -rl ${classes_dir}` echo "正在處理屬性 ${original_prop_name}....." done } checkOrCreateFile ${custom_blacklist_cfg_tmp_file} # 獲取自定義的黑名單屬性并保存到文件中 echo "" > ${custom_blacklist_cfg_file} for (( i = 0; i < ${#custom_blacklist_search_dirs[@]}; i++ )); do custom_blacklist_search_dir=${custom_blacklist_search_dirs[${i}]} ./GetAndStoreProperties.sh \ -i ${custom_blacklist_search_dir}\ -o ${custom_blacklist_cfg_tmp_file} cat ${custom_blacklist_cfg_tmp_file} >> ${custom_blacklist_cfg_file} done # 獲取和保存屬性到熟悉配置文件 ./GetAndStoreProperties.sh \ -i ${classes_dir}\ -o ${cfg_file}\ -f \ -c ${custom_blacklist_cfg_file} # 執行屬性重命名 rename_properties echo "done."
以上是“iOS如何通過shell腳本批量修改屬性”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。