find_js_functions.sh Useful
Search directory for JS functions and their execution.
Useful for finding unused functions.
Identify normal, arrow, and async functions in the search.
Execute with -h to get help and additional instructions
wget https://git.christianimmanuel.de/scripts/raw/Useful/find_js_functions.sh && chmod 740 find_js_functions.shfind_js_functions.sh raw
#!/bin/bash
##########################################
### Search directory for JS functions and their execution.
### Useful for finding unused functions.
### Identify normal, arrow, and async functions in the search.
### Execute with -h to get help and additional instructions
cols=$(tput cols)
header_line=$(printf '─%.0s' {1..38})
find_functions_dir="/tmp/findFunctions"
found_functions_dir="/tmp/foundFunctions"
final_file="$found_functions_dir/result.txt"
unused_file="$found_functions_dir/unused.txt"
questionable_file="$found_functions_dir/questionable.txt"
main_director=""
declare -A pid_files
declare -a file_array=()
declare -a ignore_file_array=()
use_parallel=false
sorted_paths=""
extensions="php,html,js"
global_functions=""
TOTAL=0
print_help() {
echo "------------------------------------------------------"
echo "find_js_functions.sh - Find and Analyze JavaScript Functions"
echo "------------------------------------------------------"
echo
echo "Description:"
echo "The find_js_functions.sh script is searching and analyzing JavaScript functions within files."
echo
echo "Usage:"
echo "To use the script, provide the desired options and file paths using the command line. Here is the general format:"
echo
echo " find_js_functions.sh [options] [directory]"
echo
echo "Options:"
echo " -a Search for all files in the specified directories."
echo " -e <ext> Specify file extensions to search for (comma-separated)."
echo " Default: $extensions."
echo " -f <files> Specify files to search for."
echo " -h Display the help information."
echo " -x <names> Specify function names to search for (comma-separated)."
echo " -u <file> Specify the path to the unused functions output file."
echo " Default: $unused_file."
echo " -q <file> Specify the path to the questionable functions output file."
echo " Default: $questionable_file."
echo " -o <file> Specify the path to the output file."
echo " Default: $final_file."
echo " -i <files/dirs> Specify files or directories to ignore (comma-separated)."
echo
echo "Example:"
echo " find_js_functions.sh -e js,xhtml -x func1,func2,func3 -u unused.txt -q questionable.txt -i ignore.js,path/ignore /path/to/directory"
echo
echo "Note: If the 'directory' parameter is not provided, the script will search in the current directory."
}
# Function to clean exportet vars
cleanup() {
unset find_functions_dir found_functions_dir final_file unused_file questionable_file main_director
unset -f clear_php_files search_execution
exit 1
}
# Trap the SIGINT signal (Ctrl+C) and call the cleanup function
trap cleanup SIGINT
clear_php_files() {
# Set the input file name
input_file="$1"
if [[ ! $file == *.php ]]; then
cat "$file"
return
fi
# Set a flag to indicate if we are inside the block to be removed
inside_block=0
# Read the file line by line
while IFS= read -r line; do
# Check if the line contains "<?php"
if [[ $line == *"<?php"* ]]; then
# Set the flag to indicate that we are inside the block
inside_block=1
# Print the line
echo "$line"
elif [[ $line == *"?>"* ]]; then
# Set the flag to indicate that we are outside the block
inside_block=0
# Print the line
echo "$line"
elif [ "$inside_block" -eq 0 ]; then
# Print the line if we are outside the block
echo "$line"
fi
done <<< "$(sed "s/<?php.*?>/<PHP>/g" "$input_file")"
}
# Fix paths in the given arrays
check_paths() {
fix_path() {
local -n array_name=$1
for ((i=0; i<${#array_name[@]}; i++)); do
local path="${array_name[$i]}"
if [[ ! $path = /* ]]; then
array_name[$i]="$main_directory/$path"
fi
done
}
fix_path file_array
fix_path ignore_file_array
}
# Function to remove files that match ignore patterns
remove_ignored_files() {
local filtered_files=()
for file in "${file_array[@]}"; do
local ignore_match=false
for ignore_file in "${ignore_file_array[@]}"; do
if [[ "$file" == "$ignore_file" || "$file" == "$ignore_file"* ]]; then
ignore_match=true
break
fi
done
if [[ "$ignore_match" == false ]]; then
filtered_files+=("$file")
fi
done
file_array=("${filtered_files[@]}")
}
search_files() {
local directory="$1"
IFS=',' read -ra ext_array <<< "$extensions"
# Construct the -name option for each extension
name_options=""
for extension in "${ext_array[@]}"; do
name_options+=" -o -name \"*.$extension\""
done
# Remove the leading "-o" from the name_options
name_options="${name_options# -o}"
# Use the constructed name_options in the find command
find_cmd="find \"$directory\" -type f \($name_options \) | sort"
local files=$(eval "$find_cmd")
# Save the files in the global array
while IFS= read -r file; do
file_array+=("$file")
done <<< "$files"
}
# Function to modify the file path
modify_filepath() {
# Remove the initial directory path
filepath="${1#$main_directory/}"
echo "$filepath"
}
# Function to search for arrow functions
search_for_arrow_functions() {
local filepath="$1"
# Search for arrow functions using grep and sed, remove leading spaces
if [[ $filepath == *.php ]]; then
sed '/<\?php/,/\?>/d' "$filepath" | grep -Eo '^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*)?(async[[:space:]]*)?\([^)]*\)[[:space:]]*=>[[:space:]]*' | sed -E 's/^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*)?(async[[:space:]]*)?\(([^)]*)\)[[:space:]]*=>[[:space:]]*$/\0/' | sed 's/^[ \t]*//;s/[ \t]*$//'
sed '/<\?php/,/\?>/d' "$filepath" | grep -o 'function [a-zA-Z_][a-zA-Z0-9_]*([^)]*) {'
else
grep -Eo '^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*)?(async[[:space:]]*)?\([^)]*\)[[:space:]]*=>[[:space:]]*' "$filepath" | sed -E 's/^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*[[:space:]]*=[[:space:]]*)?(async[[:space:]]*)?\(([^)]*)\)[[:space:]]*=>[[:space:]]*$/\0/' | sed 's/^[ \t]*//;s/[ \t]*$//'
grep -o 'function [a-zA-Z_][a-zA-Z0-9_]*([^)]*) {' "$filepath"
fi
}
# Function to search for function execution
search_execution() {
local file="$1"
local func_name="$2"
local in_file="$3"
local func_count_file="$4"
local functions
local count=0
# Store the function matches in a variable
functions=$(clear_php_files "$file" | sed -E "s/\bfunction[[:space:]]+$func_name\(/\(/g" | grep -no "$func_name([^)]*)" | sed 's/^\([0-9]\+\):/\1 \t/' | sed 's/^/ /')
if [ -z "$functions" ]; then
functions=$(clear_php_files "$file" | sed -E "s/\b${func_name}[[:space:]]+=//g; s/\bfunction[[:space:]]+\b${func_name}//g" | grep -E "(^|[,[:space:]]|[()])($func_name)($|[,[:space:]]|[()])" | grep -v "^$" | sed 's/^\([0-9]\+\):/\1 \t/' | sed 's/^/ /')
if [[ ! -f "$find_functions_dir/$func_name.lock" ]]; then
if ! grep -qF "$func_name -> $in_file" "$questionable_file"; then
echo "$func_name -> $in_file" >> "$questionable_file"
fi
fi
else
touch "$find_functions_dir/$func_name.lock"
if grep -qF "$func_name -> $in_file" "$questionable_file"; then
sed -i "/$func_name -> .*$/d" "$questionable_file"
fi
fi
# Output the matches
if [ -n "$functions" ]; then
echo -e " \033[1;36mFile:\033[0m ${file#$main_directory/}"
echo "$functions"
count=$(wc -l <<< "$functions")
fi
# Save the function count to a temporary file
echo "$count" >> "$func_count_file"
}
# Function to search all files for function execution
search_for_function_execution() {
local line="$1"
local input_file="$2"
local function_name=$(echo "$line" | awk '{print $1}')
if [[ "$function_name" = "function" ]]; then
function_name=$(echo "$line" | awk '{print $2}' | cut -d'(' -f1 )
fi
local functions_count=0
if [[ -z "$function_name" ]]; then
return
fi
if [[ "$use_parallel" == "true" ]]; then
functions_count_file="$(mktemp)"
parallel --will-cite search_execution {} "$function_name" "$input_file" "$functions_count_file" ::: "${file_array[@]}"
functions_count=$(awk '{sum += $1} END {print sum}' "$functions_count_file")
rm "$functions_count_file"
else
functions_count_file="$(mktemp)"
for file in "${file_array[@]}"; do
search_execution "$file" "$function_name" "$input_file" "$functions_count_file"
done
functions_count=$(awk '{sum += $1} END {print sum}' "$functions_count_file")
rm "$functions_count_file"
fi
if [ "$functions_count" -eq 0 ]; then
echo -e " \033[1;31mCount: $functions_count\033[0m"
else
echo -e " \033[1;33mCount: $functions_count\033[0m"
fi
# If no functions were found, write to unused_functions file
if [ $functions_count -eq 0 ]; then
echo "$function_name -> $input_file" >> "$unused_file"
fi
}
# Function to sort the unused_functions file by file path
sort_unused_functions() {
if [ -s "$unused_file" ]; then
sort -t '>' -k 2 -o "$unused_file" "$unused_file"
fi
}
# Function to iterate through files
iterate_file() {
local file="$1"
local functions
if [ -n "$global_functions" ]; then
functions="$global_functions"
else
functions=$(search_for_arrow_functions "$file")
fi
search_res=""
while IFS= read -r line; do
search="$(search_for_function_execution "$line" "$file")"
if [ -n "$search" ]; then
search_res="$search_res \033[1;32mFunction:\033[0m $line\n$search\n\n"
fi
done <<< "$functions"
output_file="${file//\//-}.txt"
if [ -n "$search_res" ]; then
if [ ! -s "$filename" ]; then
echo -e "\n\033[1m${header_line}\033[0m" >> "$find_functions_dir/$output_file"
echo -e "\033[1m=== File: $(modify_filepath $file)\033[0m\n" >> "$find_functions_dir/$output_file"
fi
echo -e "$search_res" | sed '/^$/d' >> "$find_functions_dir/$output_file"
else
echo -e "$(modify_filepath $file) has no functions" | sed '/^$/d' >> "$find_functions_dir/$output_file"
fi
}
# Function to iterate through files and subdirectories
iterate_directory() {
local dir="$1"
local title="$2"
# Print the sorted file paths with a title
if [ ${#file_array[@]} -gt 0 ]; then
title="${title/main}"
# Iterate through the sorted file paths
process_file() {
file="$1"
SECONDS=0
iterate_file "$file"
output_file="${file//\//-}.txt"
if [ -s "$find_functions_dir/$output_file" ]; then
cat "$find_functions_dir/$output_file"
cat "$find_functions_dir/$output_file" >> "$final_file"
fi
elapsed_time=$SECONDS
# Format the output
if [ $SECONDS -gt 0 ]; then
minutes=$((elapsed_time / 60))
seconds=$((elapsed_time % 60))
printf "Runtime: %02d:%02d\n" "$minutes" "$seconds"
fi
}
if [ -n "$global_functions" ]; then
process_file "all"
else
for file in "${file_array[@]}"; do
process_file "$file"
done
fi
fi
}
# Function to print the unused functions in a table-like format
print_unused_functions() {
if [ -s "$unused_file" ]; then
sort_unused_functions
echo -e "\n\033[1;31m${header_line}\033[0m"
echo -e "\033[1;31m=== Not found\033[0m\n"
# Get the longest function name within the unused functions
longest_function_name=$(awk -F " -> " '{ print length($1) }' "$unused_file" | sort -nr | head -1)
longest_function_name=$((longest_function_name + 1)) # Add 1 for spacing
# Print the table header
printf "%-${longest_function_name}s%s\n" "Function Name" "Input File"
printf "%-${longest_function_name}s%s\n" "-------------" "----------"
# Print each unused function with proper spacing
while IFS= read -r line; do
function_name=$(echo "$line" | awk -F " -> " '{ print $1 }')
input_file=$(echo "$line" | awk -F " -> " '{ print $2 }')
printf "%-${longest_function_name}s%s\n" "$function_name" "$input_file"
done < "$unused_file"
# Get the line count of the unused functions file
line_count=$(wc -l < "$unused_file")
# Print the unused functions line count in red
echo -e "\nCount: $line_count"
else
echo -e "\n\033[0;31m${header_line}\033[0m"
echo -e "\033[32mNo unused functions found.\033[0m"
fi
}
# Function to print the unused functions in a table-like format
print_questionable_functions() {
if [ -s "$questionable_file" ]; then
echo -e "\n\033[1;33m${header_line}\033[0m"
echo -e "\033[1;33m=== Questionable found\033[0m\n"
# Get the longest function name within the unused functions
longest_function_name=$(awk -F " -> " '{ print length($1) }' "$questionable_file" | sort -nr | head -1)
longest_function_name=$((longest_function_name + 1)) # Add 1 for spacing
# Print the table header
printf "%-${longest_function_name}s%s\n" "Function Name" "Input File"
printf "%-${longest_function_name}s%s\n" "-------------" "----------"
# Print each unused function with proper spacing
while IFS= read -r line; do
function_name=$(echo "$line" | awk -F " -> " '{ print $1 }')
input_file=$(echo "$line" | awk -F " -> " '{ print $2 }')
printf "%-${longest_function_name}s%s\n" "$function_name" "$input_file"
done < "$questionable_file"
# Get the line count of the unused functions file
line_count=$(wc -l < "$questionable_file")
# Print the unused functions line count in red
echo -e "\nCount: $line_count"
else
echo -e "\n\033[1;35m${header_line}\033[0m"
echo -e "\033[32mNo questionable functions found.\033[0m"
fi
}
clear_questionable_file() {
if [ -z "$unused_file" ] || [ -z "$questionable_file" ] || [ ! -f "$unused_file" ] || [ ! -f "$questionable_file" ]; then
exit 1
fi
temp_file=$(mktemp)
# Read each line from the unused file
while IFS= read -r unused_line; do
# Extract the function name from the unused line
unused_function=$(echo "$unused_line" | cut -d' ' -f1)
# Remove any lines from the questionable file that contain the function name
grep -vF "$unused_function" "$questionable_file" > "$temp_file"
mv "$temp_file" "$questionable_file"
done < "$unused_file" 2>/dev/null
}
# Main function
main() {
export find_functions_dir found_functions_dir final_file unused_file questionable_file main_director
export -f clear_php_files search_execution
main_directory="${1:-$(pwd)}"
# Create the directory /tmp/findFunctions if it doesn't exist and ensure it's empty
mkdir -p "$find_functions_dir"
mkdir -p "$found_functions_dir"
rm -f "$find_functions_dir"/*
rm -f "$found_functions_dir"/*
> "$final_file"
> "$unused_file"
> "$questionable_file"
# Check if the 'parallel' command exists
if command -v parallel >/dev/null 2>&1; then
read -p "The 'parallel' command is available. Do you want to use it? (y/n): " choice
case $choice in
[Yy]*)
use_parallel=true
echo "Using parallel."
;;
[Nn]*)
use_parallel=false
echo "Not using parallel."
;;
*)
echo "Using parallel."
use_parallel=true
;;
esac
else
echo "'parallel' command not found. Not using parallel."
use_parallel=false
fi
# Get the total path for file_array and remove_ignored_files
check_paths
# List all files in file_array
if [ ${#file_array[@]} -eq 0 ]; then
search_files "$main_directory"
fi
# Remove ignored files from file_array
remove_ignored_files
# Call the recursive function to iterate through the main directory and its subdirectories
iterate_directory "$main_directory" "main"
# Print the questionable functions
clear_questionable_file
echo
echo
# Print the questionable functions
print_questionable_functions
# Print the unused functions
print_unused_functions
# Provide information about the output file
sed -i 's/\x1B\[[0-9;]*[a-zA-Z]//g' "$final_file"
if [ -f "$final_file" ]; then
line_count=$(wc -l < "$final_file")
echo -e "\n\033[1m${header_line}\033[0m"
echo -e "\033[1m=== Output File Information ===\033[0m\n"
echo "Search: $final_file"
if [[ -s "$questionable_file" ]]; then
echo "Questionable: $questionable_file"
fi
echo "Unused: $unused_file"
else
echo -e "\n\033[1m${header_line}\033[0m"
echo -e "\033[1m=== Output File Information\033[0m\n"
echo "No output file found."
fi
echo
echo -e "Total functions found: $(grep "Function:" /tmp/foundFunctions/result.txt | wc -l)"
# Cleanup: Remove the /tmp/findFunctions directory and its contents
rm -rf "$find_functions_dir"
cleanup
}
# Process command line options
while getopts "e:x:ha:f:o:u:q:i:" option; do
case $option in
e)
extensions="$OPTARG"
;;
h)
print_help
exit 0
;;
x)
g_functions="$OPTARG"
;;
a)
extensions="*"
;;
f)
IFS=',' read -ra file_array <<< "$OPTARG"
;;
o)
final_file="$OPTARG"
;;
u)
unused_file="$OPTARG"
;;
q)
questionable_file="$OPTARG"
;;
i)
IFS=',' read -ra ignore_file_array <<< "$OPTARG"
;;
\?)
echo "Invalid option: -$OPTARG" >&2
print_help
exit 0
;;
esac
done
if [ -n "$g_functions" ]; then
IFS=',' read -ra func_array <<< "$g_functions"
global_functions=$(printf "%s\n" "${func_array[@]}")
fi
# Get the directory argument (remaining non-option arguments)
shift $((OPTIND - 1))
# Call the main function with the provided directory argument or default to the current working directory
( main "$1" )