4

I can drag two files into the Meld GUI. I can also click File comparison (see screenshot), then choose one file, compare that to nothing, then choose the second file. This seems roundabout. I want to compare two files, preferably in a multiselect filepicker GUI.

Screenshot showing file picker

Even better would be to right-click one or two files in Nautilus to run Meld.

Is either of those possible?

wjandrea
  • 14,504
Joshua Fox
  • 3,011

4 Answers4

6

Personally, I always run meld from the terminal, so I give the two file names there:

meld fileA fileB

Using the GUI, all you need to do is select two files. I don't see why you need to "compare it to nothing". You just select the first file and then the second:

enter image description here

terdon
  • 104,119
1

Here is a dash script for the automation of comparing [ folders/files / office/pdf documents / archives ] (by default, on Linux, the script uses Meld - for the comparison; it also generates (for comparison) a temporary file containing the contained URLs (...:\\...) in case of a .html/.htm/.pdf file parameter):

#!/bin/dash
## Supported shells: dash, bash, zsh, ksh

GetOSType () { #$1 = returns the type of the current operating system:

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!'
    printf '%s\n' 'Press Enter to exit...';
    RestoreSettings; read temp; exit_code="1"
    }>&2
fi

}

GetCurrentShell () { #$1 = returns the <current shell name> #$1 = returns the <current shell full path>

if [ -n &quot;$BASH_VERSION&quot; ]; then current_shell_name=&quot;bash&quot;;
elif [ -n &quot;$ZSH_VERSION&quot; ]; then current_shell_name=&quot;zsh&quot;;
elif [ -n &quot;$KSH_VERSION&quot; ]; then current_shell_name=&quot;ksh&quot;;
elif [ &quot;$PS1&quot; = '$ ' ]; then current_shell_name=&quot;dash&quot;;
else current_shell_name=&quot;dash&quot;; #default shell
fi
current_shell_full_path=&quot;$(which &quot;$current_shell_name&quot;)&quot;

eval $1=\&quot;\$current_shell_name\&quot;
eval $2=\&quot;\$current_shell_full_path\&quot;

}

GetTerminalEmulatorPlusLaunchFlagLinux () { #Returns <terminal_emulator> and <terminal_emulator_plus_launch_flag> in Linux:

terminal_emulator_raw=&quot;$(ps -o comm= -p &quot;$(($(ps -o ppid= -p &quot;$(($(ps -o sid= -p &quot;$$&quot;)))&quot;)))&quot;)&quot;
if [ ! &quot;${terminal_emulator_raw#*&quot;tmux&quot;*}&quot; = &quot;$terminal_emulator_raw&quot; ]; then
    msg=&quot;$(tmux display-message -p &quot;#{client_pid}&quot;)&quot;
    terminal_emulator_raw=&quot;$(ps -o comm= -p &quot;$(($(ps -o ppid= -p &quot;$(($(ps -o sid= -p &quot;$msg&quot;)))&quot;)))&quot;)&quot;
fi
terminal_emulator=&quot;${terminal_emulator_raw%&quot;-&quot;}&quot;

if [ &quot;$terminal_emulator&quot; = &quot;gnome-console&quot; ] || [ &quot;$terminal_emulator&quot; = &quot;gnome-terminal&quot; ]; then
    terminal_emulator_plus_launch_flag=&quot;\&quot;$terminal_emulator\&quot; --&quot;
elif [ &quot;$terminal_emulator&quot; = &quot;lxterminal&quot; ] || [ &quot;$terminal_emulator&quot; = &quot;qterminal&quot; ]; then
    #For LXDE and LXQt for example: the shell can be called directly (no terminal emulator needed):
    terminal_emulator_plus_launch_flag=&quot;&quot;
else
    terminal_emulator_plus_launch_flag=&quot;\&quot;$terminal_emulator\&quot; -e&quot;
fi

}

CheckCreateMainDirs () { #Check for the existence of temporary directories (used for extracting [office document / archive / pdf/htm] files) and creates them if needed:

output_dir=&quot;&quot;
error=&quot;false&quot;

{
    [ -n &quot;$TEMPORARY_EXTRACT_PATH&quot; ] &amp;&amp; cd &quot;$TEMPORARY_EXTRACT_PATH&quot; &amp;&amp; {
        output_dir=&quot;$PWD&quot;
        if [ ! -e 'TEMP_EXTR_FOLDER' ]; then
            mkdir 'TEMP_EXTR_FOLDER' || error=&quot;true&quot;
        fi
        cd 'TEMP_EXTR_FOLDER' || {
            error=&quot;true&quot;
        }
    } || error=&quot;true&quot;
} 2&gt;/dev/null
if [ &quot;$error&quot; = &quot;true&quot; ]; then
    printf '%s\n' &quot;Error: Could not access temporary folder \&quot;TEMP_EXTR_FOLDER\&quot; in the extract location: \&quot;$TEMPORARY_EXTRACT_PATH\&quot;!&quot;
    printf '%s\n' &quot;Press Enter to exit...&quot;; RestoreSettings; read temp; exit 1
fi&gt;&amp;2

}

PrintDesktopFile1 () { #Prints a .desktop file for handling parameters added for compare with Open With in Linux:

cat&lt;&lt;EOF

[Desktop Entry] Name=File Comparer Comment=Compares [ folders/files / office/pdf documents / archives ] TryExec=$terminal_emulator Exec=$terminal_emulator_plus_launch_flag "$current_shell_full_path" "$current_script_path" %U Terminal=$1 Type=Application Icon=utilities-terminal Categories=Utility; MimeType=inode/directory;application/zip;application/x-rar-compressed;application/x-7z-compressed;text/plain;application/x-tar;application/x-gtar;application/x-gzip;application/x-bzip2;application/x-xz;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tar-bz2;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 }

PrintDesktopFile2 () { #Prints a .desktop file for handling parameters added for compare with Open With in Linux - in incognito mode (i.e. a temporary session where parameters added for compare are stored only for the current temporary session):

cat&lt;&lt;EOF

[Desktop Entry] Name=File Comparer (Incognito) Comment=Compares [ folders/files / office/pdf documents / archives ] in Incognito Mode TryExec=$terminal_emulator Exec=$terminal_emulator_plus_launch_flag "$current_shell_full_path" "$current_script_path" --incognito %U Terminal=$1 Type=Application Icon=utilities-terminal Categories=Utility; MimeType=inode/directory;application/zip;application/x-rar-compressed;application/x-7z-compressed;text/plain;application/x-tar;application/x-gtar;application/x-gzip;application/x-bzip2;application/x-xz;application/x-compressed-tar;application/x-bzip-compressed-tar;application/x-tar-bz2;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 }

ExtractFirstAndLastPathComponent () { #$1 = input path #$2 = returns the first path component #$3 = returns the last path component

eval current_path=&quot;\&quot;\$$1\&quot;&quot;

first_path_component=&quot;&quot;
last_path_component=&quot;&quot;

if [ -n &quot;$current_path&quot; ]; then
    #Remove trailing '/' characters:
    while [ ! &quot;${current_path%&quot;/&quot;}&quot; = &quot;$current_path&quot; ]; do
        current_path=&quot;${current_path%&quot;/&quot;}&quot;
    done

    if [ -z &quot;$current_path&quot; ]; then
        eval current_path=\&quot;\$$1\&quot;
    fi

    last_path_component=&quot;${current_path##*&quot;/&quot;}&quot;
    first_path_component=&quot;${current_path%&quot;$last_path_component&quot;}&quot;
fi

eval $2=&quot;\&quot;\$first_path_component\&quot;&quot;
eval $3=&quot;\&quot;\$last_path_component\&quot;&quot;

}

CheckFilesFoldersToCompare () { #Checks if the <files and folders> to compare exist and are accessible:

if [ -z &quot;$files_folders_to_compare&quot; ]; then
    printf '%s\n' &quot;Please add files/folders to the comparison queue first!&quot;
    exit_code=1
else
    error=&quot;false&quot;
    IFS='

' count="0" for file in $(cat "$stored_file_paths_file_path" 2>/dev/null); do count=$(($count + 1)) if [ ! -e "$file" ] || [ ! -r "$file" ]; then printf '%s\n' "ERROR: Param [$count] does not exist or is not readable!">&2 error="true" fi done unset IFS if [ "$error" = "true" ]; then printf '\n%s\n\n' "Please: Try to readd files and compare them afterwards...">&2 exit_code=1 fi fi }

ExtractFileIfArchive () { #Output: # #output1 = contains the escaped archive file path (and is printed to <standard output>) #fileEFIA_extracted = contains : # - the extracted temporary output path after extracting the archive in case of an [office document/archive] or [pdf/htm] file # - the current path in case of regular file type (file/folder) (for extracting plain text)

eval fileEFIA=\&quot;\$$1\&quot;

if [ -z &quot;$first_time&quot; ]; then cat '/dev/null'&gt;&quot;$stored_file_paths_file_path&quot;; first_time=&quot;defined&quot;; fi

case &quot;$fileEFIA&quot; in
    *'.zip' | *'.odt' | *'.ods' | *'.odp' | *'.docx' | *.'xlsx' | *'.pptx' | *'.ppsx' | \
    *'.7z' | *'.rar' | *'.pdf' | *'.htm' | *'.html' | \
    *'.tar.'* | *'.bz2' | *'.xz' | *'.gz' | *'.tgz' | *'.tar' | \
     '/dev/null' )
        error=&quot;false&quot;
        case &quot;$fileEFIA&quot; in
            *'.pdf' )
                CheckUtilitiesAndBuildMessage --warning pdftotext pdftohtml
                if [ &quot;$warning&quot; = &quot;true&quot; ]; then ShowMessageDialog message; fi
            ;;
        esac
        if [ &quot;$error&quot; = &quot;false&quot; ]; then
            ExtractArchive fileEFIA fileEFIA_extracted
            output1=&quot;$(printf '%s' &quot;$fileEFIA_extracted&quot;|sed &quot;s/'/$Q\&quot;\$Q\&quot;$Q/g&quot;)&quot;
            printf &quot;'%s' &quot; &quot;$output1&quot;
        fi
    ;;
    * )
        output1=&quot;$(printf '%s' &quot;$fileEFIA&quot;|sed &quot;s/'/$Q\&quot;\$Q\&quot;$Q/g&quot;)&quot;
        printf &quot;'%s' &quot; &quot;$output1&quot;
        fileEFIA_extracted=&quot;$fileEFIA&quot;
    ;;
esac

printf '%s\n' &quot;$fileEFIA_extracted&quot;&gt;&gt;&quot;$stored_file_paths_file_path&quot;

}

ExtractArchive () { #$1 = the input file/folder path of the [ office document / archive / pdf/htm file ] / [ regular file/folder ] to be "extracted" #$2 = an ouput variable containing: # - the extracted temporary output path after extracting the archive in case of an [office document/archive] or [pdf/htm] file # - the current path in case of regular file type (file/folder) (for extracting plain text)

eval current_archive_path=&quot;\&quot;\$$1\&quot;&quot;

if [ &quot;$current_archive_path&quot; = '/dev/null' ]; then
    {
    cd &quot;$output_dir&quot; || error=&quot;true&quot;
    if [ &quot;$found_dir&quot; = &quot;true&quot; ]; then
        mkdir &quot;null&quot;
        cd &quot;null&quot; || error=&quot;true&quot;
        full_current_archive_extract_to_path=&quot;$output_dir/null&quot;
    else
        full_current_archive_extract_to_path=&quot;/dev/null&quot;
    fi 
    ##cat /dev/null&gt;&quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.links.txt&quot; || error=&quot;true&quot;
    } 2&gt;/dev/null
    if [ &quot;$error&quot; = &quot;true&quot; ]; then
        printf '%s\n' &quot;ERROR: Could not create/access &lt;output&gt; directory structure!&quot;
        printf '%s\n' &quot;Press Enter to exit...&quot;
        RestoreSettings; read temp; kill $$
    fi&gt;&amp;2

else
    full_current_archive_path=&quot;$current_archive_path&quot;

    ExtractFirstAndLastPathComponent current_archive_path fpc_current_archive_path lpc_current_archive_path
    cd &quot;$fpc_current_archive_path&quot;
    fpc_current_archive_path=&quot;$PWD&quot;

    ExtractFirstAndLastPathComponent fpc_current_archive_path fpc_fpc_current_archive_path lpc_fpc_current_archive_path
    current_archive_name_ext=&quot;${lpc_current_archive_path}&quot;
    if [ ! -d &quot;$current_archive_path&quot; ]; then
        current_archive_name_ext_plus=&quot;$current_archive_name_ext&quot;&quot;_extract&quot;
    else
        current_archive_name_ext_plus=&quot;$current_archive_name_ext&quot;
    fi
    full_current_archive_extract_to_path=&quot;$output_dir/&quot;'TEMP_EXTR_FOLDER'&quot;/$lpc_fpc_current_archive_path/$current_archive_name_ext_plus&quot;


    if [ ! -e &quot;$full_current_archive_extract_to_path&quot; ]; then
        {
        error=&quot;false&quot;
        cd &quot;$output_dir/&quot;'TEMP_EXTR_FOLDER' || error=&quot;true&quot;
        mkdir &quot;$lpc_fpc_current_archive_path&quot;; cd &quot;$lpc_fpc_current_archive_path&quot; || error=&quot;true&quot;
        mkdir &quot;$current_archive_name_ext_plus&quot;; cd &quot;$current_archive_name_ext_plus&quot; || error=&quot;true&quot;
        } 2&gt;/dev/null
        if [ &quot;$error&quot; = &quot;true&quot; ]; then
            printf '%s\n' &quot;ERROR: Could not create/access &lt;output&gt; directory structure!&quot;
            printf '%s\n' &quot;Press Enter to exit...&quot;
            RestoreSettings; read temp; kill $$
        fi&gt;&amp;2

        case &quot;$current_archive_path&quot; in
            *'.zip' | *'.odt' | *'.ods' | *'.odp' | *'.docx' | *.'xlsx' | *'.pptx' | *'.ppsx' )
                unzip &quot;$full_current_archive_path&quot; -d &quot;$full_current_archive_extract_to_path&quot;
            ;;
            *'.7z' )
                7z x &quot;$full_current_archive_path&quot; -o&quot;$full_current_archive_extract_to_path/&quot;
            ;;
            *'.rar' )
                unrar x &quot;$full_current_archive_path&quot; &quot;$full_current_archive_extract_to_path/&quot;
            ;;
            *'.pdf' )
                pdftotext &quot;$full_current_archive_path&quot; &quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.text.txt&quot;
                pdftohtml &quot;$full_current_archive_path&quot; &quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.html&quot;
                { cat &quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;*; }|ExtractURIsFromText --pipe&gt;&gt;&quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.links.txt&quot;
            ;;
            *'.htm' | *'.html' )
                cp &quot;$full_current_archive_path&quot; &quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.html&quot;
                { cat &quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;*; }|ExtractURIsFromText --pipe&gt;&gt;&quot;$full_current_archive_extract_to_path&quot;&quot;/&quot;&quot;temp.links.txt&quot;
            ;;
            *'.tar.bz2' )
                tar -xvjf &quot;$full_current_archive_path&quot; -C &quot;$full_current_archive_extract_to_path&quot;
            ;;
            *'.tar.xz' )
                tar -xvJf &quot;$full_current_archive_path&quot; -C &quot;$full_current_archive_extract_to_path&quot;
            ;;
            *'.tar.gz' | *'.tgz' )
                tar -xvzf &quot;$full_current_archive_path&quot; -C &quot;$full_current_archive_extract_to_path&quot;
            ;;
            *'.tar' )
                tar -xvf &quot;$full_current_archive_path&quot; -C &quot;$full_current_archive_extract_to_path&quot;
            ;;
            *'.bz2' | *'.xz' | *'.gz' )
                cp &quot;$full_current_archive_path&quot; &quot;$full_current_archive_extract_to_path&quot;
                case &quot;$current_archive_path&quot; in
                    *'.bz2' ) bzip2 &quot;$full_current_archive_extract_to_path/&quot;&quot;$current_archive_name_ext&quot; -d &quot;$full_current_archive_extract_to_path&quot;; ;;
                    *'.xz' ) xz &quot;$full_current_archive_extract_to_path/&quot;&quot;$current_archive_name_ext&quot; -d &quot;$full_current_archive_extract_to_path&quot;; ;;
                    *'.gz' ) gzip -d &quot;$full_current_archive_extract_to_path/&quot;&quot;$current_archive_name_ext&quot;; ;;
                esac
                case &quot;${current_archive_name_ext%&quot;.&quot;*}&quot; in
                    *'.tar' )
                        tar -xvf &quot;$full_current_archive_extract_to_path/${current_archive_name_ext%&quot;.&quot;*}&quot; -C &quot;$full_current_archive_extract_to_path&quot;
                        #rm &quot;$full_current_archive_extract_to_path/${current_archive_name_ext%&quot;.&quot;*}&quot;
                        rm &quot;$output_dir&quot;&quot;/&quot;'TEMP_EXTR_FOLDER'&quot;/&quot;&quot;${current_archive_name_ext%&quot;.&quot;*}&quot; #WARNING: EDIT WITH CAUTION!!!
                    ;;
                esac
            ;;
        esac &gt;/dev/null 2&gt;/dev/null
    else
        cd &quot;$full_current_archive_extract_to_path&quot;
    fi
fi
eval $2=\&quot;\$full_current_archive_extract_to_path\&quot;

}

PrintJustInTitle () { #Changes the terminal emulator window title to: $1 (survives output redirection):

printf &quot;\033]0;$1\007&quot;&gt;&quot;$print_to_screen&quot;

}

ShowMessageDialog () { #Displays a warning dialog message (using the 'zenity' GUI) containing the text in the <message> variable:

eval message=&quot;\&quot;\$$1\&quot;&quot;
zenity --warning --text=&quot;$message&quot; --no-wrap

}

CheckUtilitiesAndBuildMessage () { #Check if any of the necessary utilities is missing:

message=&quot;&quot;
msg_type=&quot;warning&quot;; msg_prefix=&quot;WARNING&quot;
warning=&quot;false&quot;
if [ &quot;$1&quot; = '--warning' ]; then
    shift
elif [ &quot;$1&quot; = '--warning-message' ]; then
    eval message=&quot;\&quot;\$$2\&quot;&quot;
    shift; shift
else
    msg_type=&quot;error&quot;; msg_prefix=&quot;ERROR&quot;
    error=&quot;false&quot;
fi

warning_all=&quot;false&quot;; error_all=&quot;false&quot;
error_or_warning_count=&quot;0&quot;
for utility; do
    which $utility &gt;/dev/null 2&gt;/dev/null || {
        error_or_warning_count=$(($error_or_warning_count + 1))
        if [ -z &quot;$message&quot; ]; then
            message=&quot;$msg_prefix: the '$utility'&quot;
        else
            message=&quot;$message&quot;&quot;, '$utility'&quot;
        fi
        eval $msg_type=&quot;\&quot;true\&quot;&quot;
        eval $msg_type\_all=&quot;\&quot;true\&quot;&quot;
    }
done

if [ &quot;$warning_all&quot; = &quot;true&quot; ] || [ &quot;$error_all&quot; = &quot;true&quot; ]; then
    if [ &quot;$error_or_warning_count&quot; = &quot;1&quot; ]; then
        message=&quot;$message&quot;&quot; utility is not installed!&quot;
    elif [ &quot;$error_or_warning_count&quot; -gt &quot;1&quot; ]; then
        message=&quot;$message&quot;&quot; utilities are not installed!&quot;
    fi
fi

if [ &quot;$error&quot; = &quot;true&quot; ]; then RestoreSettings; fi

}

RestoreSettings () { #Restores the initial IFS and directory (to their original value - that they had before running the script):

IFS=&quot;$initial_IFS&quot;
cd &quot;$initial_dir&quot;

}

ExtractURIsFromText () {

START_REC_SEQ='_@_@_@_1_2_3_'
END_REC_SEQ='_3_2_1_@_@_@_'

NON_EOL_CHARS='[^[:blank:]^&gt;^&lt;]*'

scheme_match='([a-zA-Z]+[0-9\-]*\:\/\/){1}'
domain_match='((([a-zA-Z]+[0-9\-]*)+(\.[a-zA-Z]+[0-9\-]*)+)+)'

keep_lines_not_containing_an_URI='sed -E '&quot;'&quot;'/^'&quot;$START_REC_SEQ&quot;&quot;$scheme_match&quot;&quot;$domain_match&quot;&quot;$NON_EOL_CHARS&quot;&quot;$END_REC_SEQ&quot;'$/d'&quot;'&quot;
delete_lines_not_containing_an_URI='sed -E '&quot;'&quot;'/^'&quot;$START_REC_SEQ&quot;&quot;$scheme_match&quot;&quot;$domain_match&quot;&quot;$NON_EOL_CHARS&quot;&quot;$END_REC_SEQ&quot;'$/!d'&quot;'&quot;

sed_command1='sed -E '&quot;'&quot;'s/^('&quot;$scheme_match&quot;&quot;$domain_match&quot;&quot;$NON_EOL_CHARS&quot;')$/'&quot;$START_REC_SEQ&quot;'\1'&quot;$END_REC_SEQ&quot;'/g'&quot;'&quot;
sed_command2='sed -E '&quot;'&quot;'s/('&quot;$scheme_match&quot;&quot;$domain_match&quot;&quot;$NON_EOL_CHARS&quot;')(.*)/'&quot;${NL}&quot;'\1'&quot;${NL}&quot;'\7/g'&quot;'&quot;

stored_results1=&quot;&quot;; stored_results2=&quot;&quot;
if [ &quot;$1&quot; = &quot;--pipe&quot; ]; then
    stored_results1=&quot;$(while IFS= read -r line; do printf '%s\n' &quot;$line&quot;; done;)&quot;
else
    stored_results1=&quot;$(find &quot;${@}&quot; -type f -exec cat '{}' \;)&quot;
fi
current_list_0=&quot;0&quot;; cc=&quot;0&quot;; var=&quot;&quot;; found_links=&quot;true&quot;

stored_results_all=&quot;$stored_results1&quot;
while [ &quot;$found_links&quot; = &quot;true&quot; ]; do
    stored_results2=&quot;$(printf '%s' &quot;$stored_results1&quot;|eval &quot;$sed_command1&quot;)&quot;
    if [ ! &quot;$stored_results2&quot; = &quot;$stored_results1&quot; ]; then
        var=&quot;$(printf '%s' &quot;$stored_results2&quot;|eval $delete_lines_not_containing_an_URI)&quot;
        if [ -n &quot;$var&quot; ]; then
            found_links=&quot;true&quot;
            stored_results1=&quot;$(printf '%s' &quot;$stored_results2&quot;|eval $keep_lines_not_containing_an_URI)&quot;
        else
            found_links=&quot;false&quot;
        fi
    else
        stored_results1=&quot;$(printf '%s' &quot;$stored_results2&quot;|eval &quot;$sed_command2&quot;)&quot;
        if [ ! &quot;$stored_results1&quot; = &quot;$stored_results2&quot; ]; then
            found_links=&quot;true&quot;
        else
            found_links=&quot;false&quot;
        fi
    fi
    stored_results_all=&quot;$(printf '%s' &quot;$stored_results_all&quot;|eval &quot;$sed_command2&quot;)&quot;
done
stored_results_all=&quot;$(printf '%s' &quot;$stored_results_all&quot;|eval &quot;$sed_command1&quot;)&quot;
current_list=&quot;$(printf '%s' &quot;$stored_results_all&quot;|eval $delete_lines_not_containing_an_URI)&quot;
printf &quot;%s\n&quot; &quot;$current_list&quot;|sed -E 's/'&quot;$START_REC_SEQ&quot;'(.*)'&quot;$END_REC_SEQ&quot;'/\1/g'

}

GenerateSequence () { #Prints the sequence of numbers $1 .. $2 <-> if $1 value < $2 value:

sequence_start=$(($1))
sequence_end=$(($2))
if [ &quot;$sequence_start&quot; -le &quot;$sequence_end&quot; ]; then
    seq $sequence_start $sequence_end
fi

}

Commands that might be useful:

soffice - for Office Documents - for example: convert an Office Document to a HTML Document:

soffice --headless --convert-to html <office_document_FULL_PATH> --outdir <OUTPUT_DIR_PATH>>

pdftohtml - for converting PDF Documents to HTML Documents (-i = skip image files):

pdftohtml -i <pdf_document>

- filter results - print lines containing string (highlight):

...|grep "string"

- filter results - print lines not containing string:

...|grep -v "string"

- filter results - print lines containing: string1 OR string2 OR ... stringN:

...|awk '/string1|string2|...|stringN/'

- filter results - print lines not containing: string1 OR string2 OR ... stringN:

...|awk '!/string1|string2|...|stringN/'

- filter results - print columns <1> and <2> from output:

...|awk '{print $1, $2}'

- filter results (-F = Fixed string) - print lines in '/file/path/2' that are in '/file/path/1':

grep -F -f '/file/path/1' '/file/path/2'

- filter results (-F = Fixed string) - print lines in '/file/path/2' that are not in '/file/path/1':

grep -F -vf '/file/path/1' '/file/path/2'

- group results by uniqueness - print lines that are unique:

...|sort|uniq -u

- group results by uniqueness - print lines that are duplicate:

...|sort|uniq -d

- group results by uniqueness - print lines, grouping common lines (print count in front of each line):

...|sort|uniq -c

- sort results by column <N> (replace <N> with a number (1..)):

...|sort -k <N>

- sort results by column <N> (replace <N> with a number (1..)) - as numeric sort (-n) in reversed order (-r):

...|sort -k <N> -n -r

print_to_screen='/dev/tty'

{

set +f #Enable globbing (POSIX compliant) setopt no_nomatch 2>/dev/null #Enable globbing (zsh)

CLEANUP_EXTRACTED_FILES_ON_NEW_SESSION="true" #Set this variable to "false" in order to keep the temporary EXTRACTED [ARCHIVES]/[PDF]/[HTML] FILES in the output_folder - after a compare session

Q="'"

initial_IFS="$IFS"

GetOSType OS_TYPE

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)

if [ "$OS_TYPE" = "Linux" ] || [ "$OS_TYPE" = "Other" ]; then if [ -e '/dev/shm' ]; then TEMPORARY_EXTRACT_PATH='/dev/shm' elif [ -e "$HOME" ]; then TEMPORARY_EXTRACT_PATH="$HOME" else printf '%s\n' "Please provide TEMPORARY EXTRACT PATH: ">&2 read TEMPORARY_EXTRACT_PATH fi elif [ "$OS_TYPE" = "BSD-based" ] && [ -e "$HOME" ]; then TEMPORARY_EXTRACT_PATH="$HOME" else printf '%s\n' "Please provide TEMPORARY EXTRACT PATH: " read TEMPORARY_EXTRACT_PATH fi

TEMPORARY_EXTRACT_FOLDER='TEMP_EXTR_FOLDER' # HARDCODED STRING (SHOULD NOT BE CHANGED)

CheckCreateMainDirs

desktop_file_name_ext1="FileComparerScript.desktop" #can be changed desktop_file_path1="$APPS_DESKTOP_FILES_FOLDER""/""$desktop_file_name_ext1" desktop_file_name_ext2="FileComparerScriptIncognito.desktop" #can be changed desktop_file_path2="$APPS_DESKTOP_FILES_FOLDER""/""$desktop_file_name_ext2"

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' incognito_stored_file_paths_file_path="$stored_file_paths_file_parent_dir_path""/"'FileComparerScriptSettingsIncognito.txt' temp_compared_status_file="$stored_file_paths_file_parent_dir_path""/"'temp_compared_status_file.txt' temp_to_be_reset_status_file="$stored_file_paths_file_parent_dir_path""/""temp_to_be_reset.txt"

#Store New Line for use with sed: if [ "$OS_TYPE" = "Linux" ]; then NL=$(printf '%s' "\n") elif [ "$OS_TYPE" = "BSD-based" ]; then #NL=$(printf "\n") NL="\ " fi

#Set default File Comparer: if [ "$OS_TYPE" = "Linux" ]; then compare_app="meld" #Linux elif [ "$OS_TYPE" = "BSD-based" ]; then compare_app="ksdiff" #(=call Kaleidoscope from the commandline) #macOS which $compare_app >/dev/null 2>/dev/null || { compare_app="opendiff" #(=call FileMerge from the commandline) #macOS }

fi

exit_code=0

if [ "$1" = "--install" ]; then

IFS='

'

if [ &quot;$OS_TYPE&quot; = &quot;Linux&quot; ]; then

    error=&quot;false&quot;

    CheckUtilitiesAndBuildMessage grep update-desktop-database cat ps
    if [ &quot;$error&quot; = &quot;true&quot; ]; then message=&quot;INSTALL: &quot;&quot;$message&quot;; ShowMessageDialog message; exit 1; fi

    cat '/dev/null'&gt;&quot;$desktop_file_path1&quot;
    cat '/dev/null'&gt;&quot;$desktop_file_path2&quot;

    GetTerminalEmulatorPlusLaunchFlagLinux

    #terminal_visible=false/true &lt;=&gt; not show / show: the initial launcher app (terminal) window
    #for LXDE and LXQt Desktop Environments: use &quot;true&quot;; otherwise: use &quot;false&quot;
    if [ -z &quot;$terminal_emulator_plus_launch_flag&quot; ]; then
        terminal_visible=&quot;true&quot;
    else
        terminal_visible=&quot;false&quot;
    fi

    PrintDesktopFile1 $terminal_visible&gt;&quot;$desktop_file_path1&quot;
    PrintDesktopFile2 $terminal_visible&gt;&quot;$desktop_file_path2&quot;

    # Update the database (cache file) of desktop entries (for the Open With Menu):
    update-desktop-database &quot;$APPS_DESKTOP_FILES_FOLDER&quot; || { error=&quot;true&quot;; }

    # Update the Favorite (Dock) Icons List:
    #BUG:# favorite_icons_list=$(dconf read /org/gnome/shell/favorite-apps 2&gt;/dev/null) || { error=&quot;true&quot;; }
    favorite_icons_list=$(gsettings get org.gnome.shell favorite-apps 2&gt;/dev/null) || { error=&quot;true&quot;; }
    printf '%s' &quot;$favorite_icons_list&quot;|grep -F &quot;$desktop_file_name_ext1&quot;&gt;/dev/null || {
        #BUG:# dconf write /org/gnome/shell/favorite-apps &quot;${favorite_icons_list%&quot;]&quot;}&quot;&quot;, &quot;&quot;'$desktop_file_name_ext1'&quot;&quot;]&quot;
        gsettings set org.gnome.shell favorite-apps &quot;${favorite_icons_list%&quot;]&quot;}&quot;&quot;, &quot;&quot;'$desktop_file_name_ext1'&quot;&quot;]&quot;
    } 2&gt;/dev/null || { error=&quot;true&quot;; }

    if [ &quot;$error&quot; = &quot;true&quot; ]; then
        printf '\n%s\n\n' &quot;ERROR: Install encountered errors.&quot;&gt;&amp;2
    else
        printf '\n%s\n' &quot;Installed as '$desktop_file_name_ext1' in the Desktop Favorite Apps Ribbon (Dock).&quot;
        printf '\n%s\n' &quot;Installed as '$desktop_file_name_ext1', '$desktop_file_name_ext2' in the '$APPS_DESKTOP_FILES_FOLDER' folder.&quot;
        printf &quot;\n&quot;
    fi
else
    printf '\n%s\n\n' &quot;ERROR: Currently, install is implemented only for the Linux operating systems!&quot;&gt;&amp;2
fi

fi if [ -z "$1" ]; then printf '%s\n' "true">"$temp_to_be_reset_status_file" fi if [ ! "$1" = "--install" ]; then

CheckUtilitiesAndBuildMessage mkdir cat kill unzip tar cp
if [ &quot;$error&quot; = &quot;true&quot; ]; then ShowMessageDialog message; exit 1; fi

initial_dir=&quot;$PWD&quot;

if [ ! -e &quot;$temp_to_be_reset_status_file&quot; ]; then
    cat '/dev/null'&gt;&quot;$temp_to_be_reset_status_file&quot;
fi

cd &quot;$initial_dir&quot;


if [ ! -e &quot;$stored_file_paths_file_path&quot; ]; then
    cat '/dev/null'&gt;&quot;$stored_file_paths_file_path&quot;
fi

PrintJustInTitle &quot;File Comparer&quot;

if [ ! &quot;${#@}&quot; = &quot;0&quot; ]; then

    if [ &quot;$1&quot; = &quot;--incognito&quot; ]; then
        shift;
        stored_file_paths_file_path=&quot;$incognito_stored_file_paths_file_path&quot;
    fi

    #######
    if [ -z &quot;$(cat &quot;$temp_compared_status_file&quot; 2&gt;/dev/null)&quot; ]; then
        printf '%s\n' &quot;compared&quot;&gt;&quot;$temp_compared_status_file&quot; 2&gt;/dev/null
    else
        cat '/dev/null'&gt;&quot;$temp_compared_status_file&quot; 2&gt;/dev/null
    fi

    if [ &quot;$(cat &quot;$temp_to_be_reset_status_file&quot; 2&gt;/dev/null)&quot; = &quot;true&quot; ]; then
        cat '/dev/null'&gt;&quot;$stored_file_paths_file_path&quot;
        cat '/dev/null'&gt;&quot;$incognito_stored_file_paths_file_path&quot;
        printf '%s\n' &quot;false&quot;&gt;&quot;$temp_to_be_reset_status_file&quot;
        if [ -n &quot;$TEMPORARY_EXTRACT_PATH&quot; ] &amp;&amp; [ -n &quot;$output_dir&quot; ]; then
            if [ &quot;$CLEANUP_EXTRACTED_FILES_ON_NEW_SESSION&quot; = &quot;true&quot; ]; then
                rm -R -f &quot;$output_dir&quot;&quot;/&quot;'TEMP_EXTR_FOLDER'&quot;/&quot;* #WARNING: EDIT WITH CAUTION!!!
            fi
        fi
    fi
    #######

    for file; do
        if [ &quot;$file&quot; = &quot;&quot; ]; then
            file=&quot;/dev/null&quot;
        fi
        printf '%s\n' &quot;Loading file: '$file'...&quot;
        files_folders_to_compare=$( \
            IFS='

';
for current_file in $(cat "$stored_file_paths_file_path" 2>/dev/null); do
ExtractFileIfArchive current_file;
done;
ExtractFileIfArchive file;
); done else if [ -e "$incognito_stored_file_paths_file_path" ] && [ -n "$(cat "$incognito_stored_file_paths_file_path" 2>/dev/null)" ]; then stored_file_paths_file_path="$incognito_stored_file_paths_file_path" fi

    error=&quot;false&quot;
    IFS='

';
count="0" found_dir="false" for current_file in $(cat "$stored_file_paths_file_path" 2>/dev/null); do count=$(($count + 1)) printf '%s\n' "Param [$count]: &quot;$current_file&quot;" if [ -d "$current_file" ]; then found_dir="true"; fi done if [ "$count" = "1" ]; then current_file='/dev/null' printf '%s\n' "Param [$(($count + 1))]: &quot;$current_file&quot;" fi

    count2=&quot;0&quot;
    for current_file in $(cat &quot;$stored_file_paths_file_path&quot; 2&gt;/dev/null); do
        count2=$(($count2 + 1))
        if [ ! -e &quot;$current_file&quot; ]; then
            printf '%s\n' &quot;ERROR: Param [$count2] is not accessible! (POSSIBLE FIX: Try readding files)&quot;&gt;&amp;2
            error=&quot;true&quot;;
        fi
    done

    if [ &quot;$error&quot; = &quot;true&quot; ]; then
        RestoreSettings; read temp; exit 1
    else
        files_folders_to_compare=$( \
            IFS='

';
for current_file in $(cat "$stored_file_paths_file_path" 2>/dev/null); do
ExtractFileIfArchive current_file;
done;
if [ "$count" = "1" ]; then current_file='/dev/null'; ExtractFileIfArchive current_file; fi;
); fi

    IFS='

' CheckFilesFoldersToCompare if [ "$exit_code" = "0" ]; then printf '%s\n' "compared">"$temp_compared_status_file" IFS=' ' eval "$compare_app" $files_folders_to_compare||{ printf '%s\n' "ERROR: $compare_app (see above)" printf '%s\n' "Press Enter to exit..."; RestoreSettings; read temp; exit_code="1" }>&2 else RestoreSettings; read temp; exit_code="1" fi

    if [ -e &quot;$incognito_stored_file_paths_file_path&quot; ] &amp;&amp; [ -n &quot;$(cat &quot;$incognito_stored_file_paths_file_path&quot; 2&gt;/dev/null)&quot; ]; then
        cat '/dev/null'&gt;&quot;$incognito_stored_file_paths_file_path&quot;
    fi
fi

fi IFS="$initial_IFS"

exit $exit_code

}>"$print_to_screen"

Usage:

On Linux:

  1. If not already done: run the script with the --install flag to install the .desktop files (the .desktop files are installed to: Desktop Favorite Apps Ribbon (Dock) and $HOME/.local/share/applications (for the Open With Menu entries) for quick access)

  2. Second step

    1. Right-Click the [ folders/files / office/pdf documents / archives ], choose Open With, and then choose File Comparer / File Comparer (Incognito) -> in order to add these [ folders/files / office/pdf documents / archives ] to the comparison queue

    2. (Can be run repeatedly:) Run the script with no parameters in order to perform the comparison (the file selection is memorized until running the step 2.1. again). When running the script without parameters: if the last folders/files addition was to Incognito Mode (--incognito flag), then, after the comparison: the previous total non-Incognito folders/files selection is restored for the next comparison

Designed for Linux, with macOS in mind: can be adapted for macOS.

The missing part in this script for macOS is the Dock and the Open With Menu entry - see the links below for how to manualy create those on a Mac:

Link for Dock

Link for Open With Menu

I. Marin
  • 31
  • 2
1

A simple technique for files or folders you can select easily (like in the same folder or on your desktop) is to just highlight both and then use your right click menu to "Open with Other Aplication.." and select Meld.

0

If you want to compare two files with meld from Nautilus, you can write some scripts that can be activated through the right-click menu on a file or folder. The scripting language for Nautilus scripts may be bash, python, perl or ruby, just to list some examples. In this answer I'll show a solution based on bash.

The first thing to is to create the scripts folder with the terminal command:

mkdir -p ~/.local/share/nautilus/scripts

All scripts will go into this folder. Once a script is in the folder, it will not show up in the right-click context menu until you make it executable with the terminal command:

chmod +x <script_name>

Once you have at least one executable script, then it will appear under 'scripts' in the context menu, as shown below (the image is just a reference taken from a StackExchange post, currently I don't have acces to my own Ubuntu for a more "personal" screenshot).

Example of a nautilus script from right-context menu

Now, you can add to that folder the following two files, that I have named Meld (select first item) and Meld (select second item). I have not put the .sh just for an aestethic reason (the extension appears in the context menu, and I don't like it): the presence of the shebang line at the beginning of every file it's enough for the system to understand how to execute them.

Content of Meld (select first item) file:

#!/bin/bash
#-------------------------------------------------------------------------------
# Script:  Meld (select first item)
# Author:  Lorenz Keel (AskUbuntu)
# Purpose: Define left-side compare operand for meld
# Note:    Don't forget to run: chmod +x <script_name>
#------------------------------------------------------------------------------

Exit values

STATUS_OK=0 STATUS_ERROR=1

Check meld package installation

if [ -n "$(command -v meld)" ]; then

Create temp file

TMP_FILE="/tmp/nautilus-compare.txt"

Check if the Nautilus environment variable is empty

if [ -z "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS}" ]; then # If it's blank, exit with notification of error exit "${STATUS_ERROR}" else # Remove the previous version of the temporary file if [ -f "${TMP_FILE}" ]; then rm -rf "${TMP_FILE}" fi # Put quotes around every full path while read -r FULL_PATH; do echo "'${FULL_PATH}'" | tee "${TMP_FILE}" done < <(echo "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS::-1}") fi else

Get the name of the current icon theme

ICON_THEME="$(gsettings get org.gnome.desktop.interface icon-theme | tr -d "'")"

Set the value of the notification icon

if [ -f "/usr/share/icons/${ICON_THEME}/scalable/status/dialog-error-symbolic.svg" ]; then NOTIFY_ICON="/usr/share/icons/${ICON_THEME}/scalable/status/dialog-error-symbolic.svg" else NOTIFY_ICON="error" fi

Specifies the timeout in seconds after which the dialog is closed

NOTIFY_TIMEOUT=5000 TEXT_ERROR="Installare meld per usare questo script" notify-send -t "${NOTIFY_TIMEOUT}" -i "${NOTIFY_ICON}" "${TEXT_ERROR}" fi

Exit with notification of success

exit "${STATUS_OK}"

The above files does not trigger yet meld, but simply creates a temporary files that contains the list of the files that are selected in Nautilus when you press the Meld (select first item) item in the context menu. The selected files are read from the special variable NAUTILUS_SCRIPT_SELECTED_FILE_PATHS that serves for this specific purpose. The remaining part of the script triggers a notification with the proper icon in the Ubuntu top bar in case of errros.

Content of Meld (select first item) file:

#!/bin/bash
#-------------------------------------------------------------------------------
# Script:  Meld (select second item)
# Author:  Lorenz Keel (AskUbuntu)
# Purpose: Define right-side compare operand for meld
# Note:    Don't forget to run: chmod +x <script_name>
#------------------------------------------------------------------------------

Exit values

STATUS_OK=0 STATUS_ERROR=1

Check meld package installation

if [ -n "$(command -v meld)" ]; then

Temp file location

TMP_FILE="/tmp/nautilus-compare.txt"

Check if the Nautilus environment variable is empty

if [ -z "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS}" ]; then # If it's blank, exit with notification of error exit "${STATUS_ERROR}" else if [ -f "${TMP_FILE}" ]; then # Put quotes around every full path while read -r FULL_PATH; do echo "'${FULL_PATH}'" | tee -a "${TMP_FILE}" done < <(echo "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS::-1}") else # Temp file is absent, left item has not been selected exit "${STATUS_ERROR}" fi fi

Open meld

tail -2 "${TMP_FILE}" | xargs meld &

Wait before deleting the temp file

sleep 1s && rm -rf "${TMP_FILE}" else

Get the name of the current icon theme

ICON_THEME="$(gsettings get org.gnome.desktop.interface icon-theme | tr -d "'")"

Set the value of the notification icon

if [ -f "/usr/share/icons/${ICON_THEME}/scalable/status/dialog-error-symbolic.svg" ]; then NOTIFY_ICON="/usr/share/icons/${ICON_THEME}/scalable/status/dialog-error-symbolic.svg" else NOTIFY_ICON="error" fi

Specifies the timeout in seconds after which the dialog is closed

NOTIFY_TIMEOUT=5000 TEXT_ERROR="Installare meld per usare questo script" notify-send -t "${NOTIFY_TIMEOUT}" -i "${NOTIFY_ICON}" "${TEXT_ERROR}" fi

Exit with notification of success

exit "${STATUS_OK}"

This second script fills the temporary files with the second item of the comparison and then run meld.

You can create these files with your preferred text editor, such as nano (from terminal) or gedit and gnome-text-editor (if you prefer a GUI). The important thing is to save them in the folder I mentioned at the beginning of this answer and enable execution rights on them.

These scripts have been written by myself for my personal usage, my minimal debugging showed me that they work for the simple tasks I ask them to do, I'm aware that probably they can be improved a lot.

Lorenz Keel
  • 9,511