Function is printing twice – any suggestions?

After realizing that I was trying to use the same shortcode for two different types of lists, it was easier to just separate them in two different shortcodes.

To list all posts and break them up by type:

/**
 * Shortcode for listing all tools matching tags
 * Usage: [useful-tools-all desc="true" li="true" tags="health, teens"]
 * Alternative PHP Usage: useful_tools_listall(array(desc => 'true', li => 'true', 'tags' => 'health, teens' ))
 */
function useful_tools_listall($atts){
    $atts = shortcode_atts(
        array(
            'desc'      => '',
            'li'        => '',
            'tags'      => '',
         ), 
        $atts
    );
    
    $results="";
    $all_terms = get_terms('types');

    foreach($all_terms as $term) {
        wp_reset_query();

        $args = array('post_type' => 'useful-tools',
            'tax_query' => array(
                array(
                    'taxonomy' => 'types',
                    'field' => 'slug',
                    'terms' => $term->slug,
                ),
            ),
            'tag' => $atts['tags'],
         );

        $loop = new WP_Query($args);
        if($loop->have_posts()) {
            $results .= '<h2>'.$term->name.'</h2>';
            if (($atts['li']) && ($atts['li'] == "true")) {
                $results .= '<ul class="tools-type-ul">';
            }
            while($loop->have_posts()) : $loop->the_post();

                $postID = get_the_ID();
                $desc="";
                $actualDesc = get_post_meta($postID, '_post_desc', true);
                if ($atts['desc'] == 'true' && $actualDesc != '') {
                    $desc=" - ".get_post_meta($postID, '_post_desc', true);
                }
                if (($atts['li']) && ($atts['li'] == "true")) {
                    $results .= '<li><a href="'.get_post_meta($postID, '_post_url', true).'">'.get_the_title().'</a>'.$desc.'</li>';
                } else {
                    $results .= '<a href="'.get_post_meta($postID, '_post_url', true).'">'.get_the_title().'</a>'.$desc.'<br>';
                }

            endwhile;
            if (($atts['li']) && ($atts['li'] == "true")) {
                $results .= '</ul>';
            }
        }
    }
    return $results;
}
add_shortcode('useful-tools-all', 'useful_tools_listall');

And to list posts without breaking them up by type (and without repeating them for each type):

/**
 * Shortcode for listing tools by type and tags
 * Usage: [useful-tools-only type="documents" desc="true" ul="" li="true" merge_tag="{:3:value}" value="B" tags="health, teens"]
 * Param "type" must be the taxonomy's exact slug name, not the display name, and it is case-sensitive
 * Params "ul" and "li" set to "true" if you want to include <ul> and <li> tags. 
 * Keep "ul" empty and set "li" true when using in Gravity Form confirmation.
 * Params "merge_tag" and "value" are for conditional components in Gravity Form confirmation.
 */
function useful_tools_listonly($atts){
    $atts = shortcode_atts(
        array(
            'type'      => '',
            'desc'      => '',
            'ul'        => '',
            'li'        => '',
            'merge_tag' => '',
            'value'     => '',
            'tags'      => '',
         ), 
        $atts
    );
    
    $results="";
    if (($atts['merge_tag']) && (!preg_match('/\b'.$atts['value'].'\b/', $atts['merge_tag']))) {
        $results .= '';
    } else {
        
        $myposts = get_posts(
            array(
                'showposts' => -1,
                'post_type' => 'useful-tools',
                'tax_query' => array(
                    array(
                        'taxonomy' => 'types',
                        'field' => 'slug',
                        'terms' => $atts['type'],
                    )
                ),
                'tag' => $atts['tags'],
            )
        );

        if ((!$atts['type']) || ($atts['type'] == "")) {
            $results .= '<h2>'.$atts['type'].'</h2>';
        }
        if (($atts['ul']) && ($atts['ul'] == "true")) {
            $results .= '<ul class="tools-type-ul">';
        }
        
        foreach ($myposts as $mypost) {
            
            $postID = $mypost->ID;
            $desc="";
            $actualDesc = get_post_meta($postID, '_post_desc', true);
            if ($atts['desc'] == 'true' && $actualDesc != '') {
                $desc=" - ".get_post_meta($postID, '_post_desc', true);
            }
            if (($atts['li']) && ($atts['li'] == "true")) {
                $results .= '<li><a href="'.get_post_meta($postID, '_post_url', true).'">'.$mypost->post_title.'</a>'.$desc.'</li>';
            } else {
                $results .= '<a href="'.get_post_meta($postID, '_post_url', true).'">'.$mypost->post_title.'</a>'.$desc.'<br>';
            }
            
        }
        
        if (($atts['ul']) && ($atts['ul'] == "true")) {
            $results .= '</ul>';
        }
    }
    return $results;
}
add_shortcode('useful-tools-only', 'useful_tools_listonly');

Thanks for your help @t2pe and @Tom.