#!/bin/dash
## Supported shells: dash, bash, zsh, ksh
PrintDesktopFile () {
cat<<EOF
[Desktop Entry]
Name=File Comparer
Comment=Compares [ folders/files / office/pdf documents / archives ] using 1. diff -r / 2. diff -r -q / 3. Meld
TryExec=$terminal_emulator
Exec=$terminal_emulator_plus_launch_flag "$current_shell_full_path" "$current_script_path" OPEN_WITH_MENU %U
Terminal=$1
Type=Application
Icon=utilities-terminal
Categories=Utility;
MimeType=inode/directory;text/plain;application/x-tar;application/x-gtar;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tar-bz2;application/x-bzip2;application/zip;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.presentationml.slideshow;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;application/vnd.oasis.opendocument.text;application/vnd.oasis.opendocument.spreadsheet;application/vnd.oasis.opendocument.presentation;application/pdf
EOF
}
PrintMenu () {
{
printf '\n%s\n' "Choose one option: "
printf '%s\n' "Press: "
printf '%s\n' " 1 - Compare with diff -r the previously selected files/folders"
printf '%s\n' " 2 - Compare with diff -r -q the previously selected files/folders"
printf '%s\n' " 3 - Compare with Meld the previously selected files/folders"
printf '%s\n' " 0 - EXIT script"
printf '%s\n' " - - FORCE EXIT (EXIT terminal)"
}>"$print_to_screen"
}
GetTerminalEmulatorPlusLaunchFlagLinux () {
terminal_emulator_raw="$(ps -o comm= -p "$(($(ps -o ppid= -p "$(($(ps -o sid= -p "$$")))")))")"
if [ ! "${terminal_emulator_raw#*"tmux"*}" = "$terminal_emulator_raw" ]; then
terminal_emulator_raw="$(ps -o comm= -p "$(($(ps -o ppid= -p "$(($(ps -o sid= -p "$(tmux display-message -p "#{client_pid}")")))")))")"
fi
terminal_emulator="${terminal_emulator_raw%"-"}"
if [ "$terminal_emulator" = "gnome-console" ] || [ "$terminal_emulator" = "gnome-terminal" ]; then
terminal_emulator_plus_launch_flag="\"$terminal_emulator\" --"
elif [ "$terminal_emulator" = "lxterminal" ] || [ "$terminal_emulator" = "qterminal" ]; then
#For LXDE and LXQt for example: the shell can be called directly (no terminal emulator needed):
terminal_emulator_plus_launch_flag=""
else
terminal_emulator_plus_launch_flag="\"$terminal_emulator\" -e"
fi
}
get_char () {
{
saved_tty_settings=$(stty -g); #save initial tty settings
stty raw -echo #enable special characters (erase, kill, werase, rprnt) and disable echo-ing
dd count=1 2>/dev/null #read (copy) and print: 1 input block
stty "$saved_tty_settings" #restore initial tty settings
}</dev/tty 2>/dev/null||{ printf '\n%s\n\n' "ERROR: Could not read character!">&2; }
}
PrintJustInTitle () {
printf "\033]0;$1\007">"$print_to_screen"
}
CheckFilesFoldersToCompare () {
if [ -z "$files_folders_to_compare" ]; then
printf '%s\n' "Please add files/folders to the comparison queue first!"
exit_code=1
else
error="false"
IFS='
'
for file in $(cat "$stored_file_paths_file_path"); do
if [ ! -e "$file" ] || [ ! -r "$file" ]; then
printf '%s\n' "ERROR: input file/folder: "$file" does not exist or is not readable!"
error="true"
fi
done
unset IFS
if [ "$error" = "true" ]; then
printf '\n%s\n\n' "Please: Try to readd files and compare them afterwards..."
exit_code=1
fi
fi
}
GetOSType () {
if [ -n "$1" ]; then
case "$(uname -s)" in
"Linux" )
eval $1="Linux"
;;
"Darwin" | "BSD" )
eval $1="BSD-based"
;;
* )
eval $1="Other"
;;
esac
else
printf '%s\n' 'ERROR: GetOSType: Expected 1 parameter!'>&2
{
printf '%s\n' 'Press Enter to exit...';
CleanUp; read temp;
}>"$print_to_screen"
fi
}
GetCurrentShell () {
if [ -n "$BASH_VERSION" ]; then current_shell_name="bash";
elif [ -n "$ZSH_VERSION" ]; then current_shell_name="zsh";
elif [ -n "$KSH_VERSION" ]; then current_shell_name="ksh";
elif [ "$PS1" = '$ ' ]; then current_shell_name="dash";
else current_shell_name="dash"; #default shell
fi
current_shell_full_path="$(which "$current_shell_name")"
eval $1=\"\$current_shell_name\"
eval $2=\"\$current_shell_full_path\"
}
PrintSavedBreakTimeoutFromFile() {
cat "$temp_break_timeout_state_file" 2>/dev/null
}
PrepareExitAndExit () {
if [ "$input_answer" = "1" ] || [ "$input_answer" = "2" ]; then
if [ "$OPEN_WITH_MENU" = "OPEN_WITH_MENU" ]; then
{
printf '%s\n' "Press Enter to exit...";
CleanUp; read temp;
}>"$print_to_screen"
fi
else
if [ ! "$1" = "RUN" ]; then
PrintJustInTitle ""
if [ -n "$TEMPORARY_EXTRACT_PATH" ] && [ -n "$TEMPORARY_EXTRACT_FOLDER" ]; then
rm -R -f "$output_dir/"*
fi
fi>"$print_to_screen"
fi
exit $exit_code
}
ExtractFirstAndLastPathComponent () {
eval current_path=""$$1""
first_path_component=""
last_path_component=""
if [ -n "$current_path" ]; then
#Remove trailing '/' characters:
while [ ! "${current_path%"/"}" = "$current_path" ]; do
current_path="${current_path%"/"}"
done
if [ -z "$current_path" ]; then
eval current_path=\"\$$1\"
fi
last_path_component="${current_path##*"/"}"
first_path_component="${current_path%"$last_path_component"}"
fi
eval $2="\"\$first_path_component\""
eval $3="\"\$last_path_component\""
}
ExtractFileIfArchive () {
eval fileEFIA="$$1"
if [ -z "$first_time" ]; then cat '/dev/null'>"$stored_file_paths_file_path"; first_time="defined"; fi
case "$fileEFIA" in
*'.zip' | *'.odt' | *'.ods' | *'.odp' | *'.docx' | *.'xlsx' | *'.pptx' | *'.ppsx' | *'.pdf' | *'.htm' | *'.html' | *'.tar.'* | *'.bz2' | *'.xz' | *'.gz' | *'.tgz' | *'.tar' | '/dev/null' )
error="false"
case "$fileEFIA" in
*'.pdf' )
CheckUtilities pdftotext pdftohtml
;;
esac
if [ "$error" = "false" ]; then
ExtractArchive fileEFIA fileEFIA_extracted
output1="$(printf '%s' "$fileEFIA_extracted"|sed "s/'/$Q\"\$Q\"$Q/g")"
printf "'%s' " "$output1"
fi
;;
* )
output1="$(printf '%s' "$fileEFIA"|sed "s/'/$Q\"\$Q\"$Q/g")"
printf "'%s' " "$output1"
fileEFIA_extracted="$fileEFIA"
;;
esac
printf '%s\n' "$fileEFIA_extracted">>"$stored_file_paths_file_path"
}
ExtractArchive () {
eval current_archive_path=""$$1""
if [ "$current_archive_path" = '/dev/null' ]; then
{
cd "$output_dir" || error="true"
mkdir "null"; cd "null" || error="true"
full_current_archive_extract_to_path="$output_dir/null"
cat /dev/null>"$full_current_archive_extract_to_path""/""temp.links.txt" || error="true"
} 2>/dev/null
if [ "$error" = "true" ]; then printf '%s\n' "ERROR: Could not create/access <output> directory structure!">&2; { printf '%s\n' "Press Enter to exit..."; CleanUp; read temp; kill $$; }>"$print_to_screen"; fi
else
full_current_archive_path="$current_archive_path"
ExtractFirstAndLastPathComponent current_archive_path fpc_current_archive_path lpc_current_archive_path
cd "$fpc_current_archive_path"
fpc_current_archive_path="$PWD"
ExtractFirstAndLastPathComponent fpc_current_archive_path fpc_fpc_current_archive_path lpc_fpc_current_archive_path
current_archive_name_ext="${lpc_current_archive_path}"
if [ ! -d "$current_archive_path" ]; then
current_archive_name_ext_plus="$current_archive_name_ext""_extract"
else
current_archive_name_ext_plus="$current_archive_name_ext"
fi
full_current_archive_extract_to_path="$output_dir/$lpc_fpc_current_archive_path/$current_archive_name_ext_plus"
if [ ! -e "$full_current_archive_extract_to_path" ]; then
{
error="false"
cd "$output_dir" || error="true"
mkdir "$lpc_fpc_current_archive_path"; cd "$lpc_fpc_current_archive_path" || error="true"
mkdir "$current_archive_name_ext_plus"; cd "$current_archive_name_ext_plus" || error="true"
} 2>/dev/null
if [ "$error" = "true" ]; then printf '%s\n' "ERROR: Could not create/access <output> directory structure!">&2; { printf '%s\n' "Press Enter to exit..."; CleanUp; read temp; kill $$; }>"$print_to_screen"; fi
case "$current_archive_path" in
*'.zip' | *'.odt' | *'.ods' | *'.odp' | *'.docx' | *.'xlsx' | *'.pptx' | *'.ppsx' )
unzip "$full_current_archive_path" -d "$full_current_archive_extract_to_path"
;;
*'.pdf' )
pdftotext "$full_current_archive_path" "$full_current_archive_extract_to_path""/""temp.text.txt"
pdftohtml "$full_current_archive_path" "$full_current_archive_extract_to_path""/""temp.html"
cat "$full_current_archive_extract_to_path""/"*|eval "$extract_urls_command">>"$full_current_archive_extract_to_path""/""temp.links.txt"
full_current_archive_extract_to_path="$full_current_archive_extract_to_path"
;;
*'.htm' | *'.html' )
cp "$full_current_archive_path" "$full_current_archive_extract_to_path""/""temp.html"
cat "$full_current_archive_extract_to_path""/"*|eval "$extract_urls_command">>"$full_current_archive_extract_to_path""/""temp.links.txt"
full_current_archive_extract_to_path="$full_current_archive_extract_to_path"
;;
*'.bz2' | *'.xz' | *'.gz' )
cp "$full_current_archive_path" "$full_current_archive_extract_to_path"
case "$current_archive_path" in
*'.bz2' ) bzip2 "$full_current_archive_extract_to_path/""$current_archive_name_ext" -d "$full_current_archive_extract_to_path"; ;;
*'.xz' ) xz "$full_current_archive_extract_to_path/""$current_archive_name_ext" -d "$full_current_archive_extract_to_path"; ;;
*'.gz' ) gzip -d "$full_current_archive_extract_to_path/""$current_archive_name_ext"; ;;
esac
case "${current_archive_name_ext%"."*}" in
*'.tar' )
tar -xvf "$full_current_archive_extract_to_path/${current_archive_name_ext%"."*}" -C "$full_current_archive_extract_to_path"
rm "$full_current_archive_extract_to_path/${current_archive_name_ext%"."*}"
;;
esac
;;
*'.tgz' | *'.tar' )
tar -xvf "$full_current_archive_path" -C "$full_current_archive_extract_to_path"
;;
esac >/dev/null 2>/dev/null
else
cd "$full_current_archive_extract_to_path"
fi
fi
eval $2=\"\$full_current_archive_extract_to_path\"
}
CheckUtilities () {
#Check if any of the necessary utilities is missing:
error="false"
for utility; do
which $utility>/dev/null 2>/dev/null || { printf "\n%s\n" "ERROR: the '$utility' utility is not installed!">&2; error="true"; }
done
if [ "$error" = "true" ]; then
{
printf "\n%s\n" "Press Enter to exit...">&2
CleanUp; read temp;
kill -s PIPE -- -$$; #kill all children processes, suppressing "Terminated" message
}>"$print_to_screen"
fi
}
CleanUp () {
IFS="$initial_IFS"
cd "$initial_dir"
}
set +f #Enable globbing (POSIX compliant)
setopt no_nomatch 2>/dev/null #Enable globbing (zsh)
Q="'"
initial_IFS="$IFS"
cd "${0%/}" 2>/dev/null
current_script_path="$(pwd -P)/${0##/}"
GetCurrentShell current_shell_name current_shell_full_path
APPS_DESKTOP_FILES_FOLDER="$HOME/.local/share/applications" #(for Linux) this is the default location for .desktop files (should not be changed)
desktop_file_name_ext="FileComparerScript.desktop" #(for Linux) can be changed
desktop_file_path="$APPS_DESKTOP_FILES_FOLDER""/""$desktop_file_name_ext" #(for Linux)
stored_file_paths_file_parent_dir_path="$APPS_DESKTOP_FILES_FOLDER" #can be changed
if [ ! -e "$stored_file_paths_file_parent_dir_path" ]; then
stored_file_paths_file_parent_dir_path="$HOME" #can be changed
fi
stored_file_paths_file_path="$stored_file_paths_file_parent_dir_path""/"'FileComparerScriptSettings.txt'
temp_break_timeout_state_file="$stored_file_paths_file_parent_dir_path""/"'temp_break_timeout_state_file.txt'
temp_compared_status_file="$stored_file_paths_file_parent_dir_path""/"'temp_compared_status_file.txt'
temp_to_be_resetted_status_file="$stored_file_paths_file_parent_dir_path""/""temp_to_be_resetted.txt"
NL2=$(printf '%s' "\n\n") #Store New Line for use with sed
insert_NL_before_URLs_command='sed -E '"'"'s/([a-zA-Z]://)/'"\${NL2}"'\1/g'"'"
strip_NON_URL_text_command='sed -E '"'"'s/((.([^a-zA-Z+])|([a-zA-Z])))(([a-zA-Z])://)([^ ^'"${TAB}"'^>^<])./\4\5\7/g'"'"
delete_lines_not_containing_an_URL='sed -E '"'"'/.://.*/!d'"'"
extract_urls_command="$insert_NL_before_URLs_command|$strip_NON_URL_text_command|$delete_lines_not_containing_an_URL"
print_to_screen='/dev/tty'
CR=$(printf '\r')
default_menu_option="1" #can be changed [ 1..3 ]
GetOSType OS_TYPE
exit_code=0
OPEN_WITH_MENU=""; if [ "$1" = "OPEN_WITH_MENU" ]; then OPEN_WITH_MENU="OPEN_WITH_MENU"; shift; fi
if [ "$1" = "--install" ]; then
IFS='
'
if [ "$OS_TYPE" = "Linux" ]; then
{
CheckUtilities grep update-desktop-database cat ps
cat '/dev/null'>"$desktop_file_path"
GetTerminalEmulatorPlusLaunchFlagLinux
#terminal_visible=false/true <=> not show / show: the initial launcher app (terminal) window
#for LXDE and LXQt Desktop Environments: use "true"; otherwise: use "false"
if [ -z "$terminal_emulator_plus_launch_flag" ]; then
terminal_visible="true"
else
terminal_visible="false"
fi
PrintDesktopFile $terminal_visible>"$desktop_file_path"
favorite_icons_list=$(dconf read /org/gnome/shell/favorite-apps 2>/dev/null)
printf '%s' "$favorite_icons_list"|grep -F "$desktop_file_name_ext">/dev/null||{
dconf write /org/gnome/shell/favorite-apps "${favorite_icons_list%"]"}"", ""'$desktop_file_name_ext'""]"
} 2>/dev/null
# Update database of desktop entries cache:
update-desktop-database "$APPS_DESKTOP_FILES_FOLDER"
} && {
printf '\n%s\n\n' "Installed in the Desktop Favorite Apps Ribbon."
} || {
printf '\n%s\n\n' "ERROR: Install encountered errors."
}
else
printf '\n%s\n\n' "ERROR: Currently, install is implemented only for the Linux operating systems!"
fi
fi
if [ -z "$1" ]; then
printf '%s\n' "true">"$temp_to_be_resetted_status_file"
fi
if [ ! "$1" = "--install" ]; then
CheckUtilities mkdir cat diff kill stty dd unzip tar cp
{
[ "$OS_TYPE" = "Linux" ] && [ -e '/dev/shm' ] && {
TEMPORARY_EXTRACT_PATH='/dev/shm'
} 2>/dev/null || {
[ "$OS_TYPE" = "BSD-based" ] && [ -e "$HOME" ] && {
TEMPORARY_EXTRACT_PATH="$HOME"
}
} || {
printf "Please provide TEMPORARY_EXTRACT_PATH: ">&2; read TEMPORARY_EXTRACT_PATH>"$print_to_screen";
}
TEMPORARY_EXTRACT_FOLDER='TEMP_EXTR_FOLDER'
}>/dev/null
initial_dir="$PWD"
output_dir=""
error="false"
{
[ -n "$TEMPORARY_EXTRACT_PATH" ] && cd "$TEMPORARY_EXTRACT_PATH" && {
if [ ! -e "$TEMPORARY_EXTRACT_FOLDER" ]; then
mkdir "$TEMPORARY_EXTRACT_FOLDER" || error="true"
fi
cd "$TEMPORARY_EXTRACT_FOLDER" && output_dir="$PWD" || {
error="true"
}
} || error="true"
} 2>/dev/null
if [ "$error" = "true" ]; then
printf '%s\n' "Error: Could not access temporary folder \"$TEMPORARY_EXTRACT_FOLDER\" in the extract location: \"$TEMPORARY_EXTRACT_PATH\"!">&2
{ printf '%s\n' "Press Enter to exit..."; CleanUp; read temp; }>"$print_to_screen"; exit 1
fi
cd "$initial_dir"
if [ ! -e "$stored_file_paths_file_path" ]; then
cat '/dev/null'>"$stored_file_paths_file_path"
fi
PrintJustInTitle "File Comparer"
if [ -e "$1" ]; then
#######
if [ -z "$(cat "$temp_compared_status_file" 2>/dev/null)" ]; then
printf '%s\n' "compared">"$temp_compared_status_file" 2>/dev/null
else
cat '/dev/null'>"$temp_compared_status_file" 2>/dev/null
fi
if [ "$(cat "$temp_to_be_resetted_status_file")" = "true" ]; then
cat '/dev/null'>"$stored_file_paths_file_path"
printf '%s\n' "false">"$temp_to_be_resetted_status_file"
fi
#######
for file; do
files_folders_to_compare=$( \
IFS='
';
for current_file in $(cat "$stored_file_paths_file_path"); do
ExtractFileIfArchive current_file;
done;
ExtractFileIfArchive file;
);
done
else
if [ ! "$1" = "RUN" ]; then
files_folders_to_compare=$( \
if [ -n "$1" ]; then \
IFS="$initial_IFS"; \
for file; do \
ExtractFileIfArchive file; \
done; \
else \
IFS='
';
for file in $(cat "$stored_file_paths_file_path"); do
ExtractFileIfArchive file;
done;
fi;
);
fi
if [ -z "$3" ]; then
files_folders_to_compare=$(
IFS='
';
for current_file in $(cat "$stored_file_paths_file_path"); do
ExtractFileIfArchive current_file;
done;
);
fi
input_answer="$2"
if [ -z "$input_answer" ] || [ ! "$1" = "RUN" ]; then
PrintMenu
printf '%s\n' " Press other keys (e.g. arrows) during the first 1 second"
printf '%s\n' " to cancel automatically loading the default option ($default_menu_option)..."
printf '%s' " "
fi>"$print_to_screen"
break_timeout_state="STATE1"
break_timeout_state="$(PrintSavedBreakTimeoutFromFile)"
if [ ! "$break_timeout_state" = "STATE3" ]; then
cat '/dev/null'>"$temp_break_timeout_state_file"
printf "STATE1">"$temp_break_timeout_state_file"
else
printf "STATE2">"$temp_break_timeout_state_file"
fi
{
sleep 1;
break_timeout_state="$(PrintSavedBreakTimeoutFromFile)"
if [ "$break_timeout_state" = "STATE1" ]; then
printf "STATE3">"$temp_break_timeout_state_file"
#stty -F /dev/tty sane
eval "$current_shell_full_path" \"\$current_script_path\" $OPEN_WITH_MENU RUN "$default_menu_option"
exit 0
fi
}&
pid=$!
break_timeout_state="$(PrintSavedBreakTimeoutFromFile)"
while [ ! "$break_timeout_state" = "STATE3" ] && [ ! "$input_answer" = "1" ] && [ ! "$input_answer" = "2" ] && [ ! "$input_answer" = "3" ] && [ ! "$input_answer" = "0" ] && [ ! "$input_answer" = "-" ]; do
input_answer=""
input_answer=$(get_char)
if [ "$break_timeout_state" = "STATE1" ]; then
eval kill -s PIPE $pid>/dev/null 2>/dev/null #kill the background process denoted here by $pid, suppressing "Terminated" message
break_timeout_state="STATE2"
printf "STATE2">"$temp_break_timeout_state_file"
fi
done
##
IFS='
'
case $input_answer in
"1" )
printf '\n%s\n' "1. diff -r"
printf '%s\n' "diff -r $files_folders_to_compare"
printf "\n"
CheckFilesFoldersToCompare
if [ "$exit_code" = "0" ]; then
printf '%s\n' "compared">"$temp_compared_status_file"
IFS='
'
diff_output="$(eval diff -r $files_folders_to_compare)"
error_code=$?
if [ -z "$diff_output" ] && [ "$error_code" = "0" ]; then
printf '%s\n' "NO DIFFERENCES ENCOUNTERED..."
[ ! "$2" = "1" ] && printf '\n%s\n' "DONE.">"$print_to_screen"
else
printf '%s\n' "$diff_output"
[ ! "$2" = "1" ] && printf '\n%s\n' "DONE.">"$print_to_screen"
fi
fi
if [ "$default_menu_option" = "1" ]; then if [ ! "$2" = "$default_menu_option" ]; then PrepareExitAndExit; fi; else PrepareExitAndExit; fi
;;
"2" )
printf '\n%s\n' "2. diff -r -q"
printf '%s\n' "diff -r -q $files_folders_to_compare"
#printf "\n"
CheckFilesFoldersToCompare
if [ "$exit_code" = "0" ]; then
printf '%s\n' "compared">"$temp_compared_status_file"
IFS='
'
diff_output="$(eval diff -r -q $files_folders_to_compare)"
error_code=$?
if [ -z "$diff_output" ] && [ "$error_code" = "0" ]; then
printf '%s\n' "NO DIFFERENCES ENCOUNTERED..."
[ ! "$2" = "2" ] && printf '\n%s\n' "DONE.">"$print_to_screen"
else
printf '%s\n' "$diff_output"
[ ! "$2" = "2" ] && printf '\n%s\n' "DONE.">"$print_to_screen"
fi
fi
if [ "$default_menu_option" = "2" ]; then if [ ! "$2" = "$default_menu_option" ]; then PrepareExitAndExit; fi; else PrepareExitAndExit; fi
;;
"3" )
printf '\n%s\n' "3. Meld"
printf '%s\n' "meld $files_folders_to_compare"
printf "\n"
CheckFilesFoldersToCompare
if [ "$exit_code" = "0" ]; then
printf '%s\n' "compared">"$temp_compared_status_file"
IFS='
'
eval meld $files_folders_to_compare 2>/dev/null||{
printf '%s\n' "ERROR: Meld is not installed?">&2
{
printf '%s\n' "Press Enter to exit...";
CleanUp; read temp;
}>"$print_to_screen"
exit_code=1
}
fi
if [ "$default_menu_option" = "3" ]; then if [ ! "$2" = "$default_menu_option" ]; then PrepareExitAndExit; fi; else PrepareExitAndExit; fi
;;
"0" | "-" )
if [ "$input_answer" = "0" ]; then printf "\n">"$print_to_screen"; exit 0; elif [ "$input_answer" = "-" ]; then printf "\n">"$print_to_screen"; kill -s PIPE -- -$$; fi
;;
esac
fi
fi 2>&1|sed "s/$/$CR/"
IFS="$initial_IFS"