[GH-ISSUE #7943] Extra command line option on ollama list #5084

Closed
opened 2026-04-12 16:10:51 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @agileandy on GitHub (Dec 5, 2024).
Original GitHub issue: https://github.com/ollama/ollama/issues/7943

When working with lots of different Ollama models it can be difficult to get some sense out of a long list. A sort option would be great on ollama list

e.g.

ollama list --size -a | -d
Sort all model by size either ascending or descending. Would need to convert all models to a common display size such as GB

ollama list --name -a | -d
Sort all models by name, again either descending of ascending.

ollama list
Can remain defaulting to the Modified ascedning.

Originally created by @agileandy on GitHub (Dec 5, 2024). Original GitHub issue: https://github.com/ollama/ollama/issues/7943 When working with lots of different Ollama models it can be difficult to get some sense out of a long list. A sort option would be great on **ollama list** e.g. > **ollama list --size -a | -d** Sort all model by size either ascending or descending. Would need to convert all models to a common display size such as GB > **ollama list --name -a | -d** Sort all models by name, again either descending of ascending. > **ollama list** Can remain defaulting to the Modified ascedning.
GiteaMirror added the feature request label 2026-04-12 16:10:51 -05:00
Author
Owner

@rick-github commented on GitHub (Dec 5, 2024):

#!/usr/bin/env python3

import ollama
import argparse
from operator import attrgetter
from datetime import datetime

def humanBytes(b):
  if b >= 1000 ** 4:
    value = b / 1000 ** 4
    unit = "TB"
  elif b >= 1000 ** 3:
    value = b / 1000 ** 3
    unit = "GB"
  elif b >= 1000 ** 2:
    value = b / 1000 ** 2
    unit = "MB"
  elif b >= 1000:
    value = b / 1000
    unit = "KB"
  else:
    return f"{b} B"
  if value >= 100:
    return f"{int(value)} {unit}"
  if value >= 10:
    return f"{int(value)} {unit}"
  if value != int(value):
    return f"{value:.1f} {unit}"
  return f"{int(value)} {unit}"

def humanDuration(t):
  seconds = t.total_seconds()
  if seconds < 1:
    return "Less than a second"
  elif seconds == 1:
    return "1 second"
  elif seconds < 60:
    return f"{seconds} seconds"
  minutes = int(seconds / 60)
  if minutes == 1:
    return "About a minute"
  if minutes < 60:
    return f"{minutes} minutes"
  hours = int(round(seconds / 3600))
  if hours == 1:
    return "About an hour"
  if hours < 48:
    return f"{hours} hours"
  if hours < 24*7*2:
    return f"{int(hours/24)} days"
  if hours < 24*30*2:
    return f"{int(hours/24/7)} weeks"
  if hours < 24*365*2:
    return f"{int(hours/24/30)} months"
  return f"{int(hours/24/365)} years"
    
def humanTime(t):
  if t == 0:
    return "Never"
  delta = datetime.now().astimezone() - t
  if int(delta.total_seconds()/3600/24/365) < -20:
    return "Forever"
  elif delta.total_seconds() < 0:
    return f"{humanDuration(-delta)} from now"
  return f"{humanDuration(delta)} ago"

def parseArgs():
  parser = argparse.ArgumentParser()
  parser.add_argument("-n", "--name", help="Sort by name", default=False, action="store_true")
  parser.add_argument("-s", "--size", help="Sort by size", default=False, action="store_true")
  parser.add_argument("-m", "--modified", help="Sort by modified date", default=False, action="store_true")
  parser.add_argument("-i", "--id", help="Sort by id", default=False, action="store_true")
  parser.add_argument("-a", "--ascending", help="Sort in ascending order", default=True, dest='sort_order', action="store_true")
  parser.add_argument("-d", "--descending", help="Sort in descending order", dest='sort_order', action="store_false")
  parser.add_argument("filter", nargs='*')
  return parser.parse_args()

def showList(args):
  sort_key = "model" if args.name else "size" if args.size else "digest" if args.id else "modified_at"
  model_list = ollama.Client().list().models
  if args.filter:
    model_list = [model for model in model_list if any([model.model.startswith(f) for f in args.filter])]
  sorted_list = sorted(model_list, key=attrgetter(sort_key), reverse=args.sort_order)
  width = max([len(model.model) for model in sorted_list])
  fmt = f"{{{0}:<{width}}}    {{{1}:<16}}{{{2}:<11}}{{{3}}}"
  print(fmt.format("NAME", "ID", "SIZE", "MODIFIED"))
  for model in sorted_list:
    print(fmt.format(model.model, model.digest[:12], humanBytes(model.size), humanTime(model.modified_at)))

def main():
  showList(parseArgs())

if __name__ == "__main__":
    main()
<!-- gh-comment-id:2521124944 --> @rick-github commented on GitHub (Dec 5, 2024): ```python #!/usr/bin/env python3 import ollama import argparse from operator import attrgetter from datetime import datetime def humanBytes(b): if b >= 1000 ** 4: value = b / 1000 ** 4 unit = "TB" elif b >= 1000 ** 3: value = b / 1000 ** 3 unit = "GB" elif b >= 1000 ** 2: value = b / 1000 ** 2 unit = "MB" elif b >= 1000: value = b / 1000 unit = "KB" else: return f"{b} B" if value >= 100: return f"{int(value)} {unit}" if value >= 10: return f"{int(value)} {unit}" if value != int(value): return f"{value:.1f} {unit}" return f"{int(value)} {unit}" def humanDuration(t): seconds = t.total_seconds() if seconds < 1: return "Less than a second" elif seconds == 1: return "1 second" elif seconds < 60: return f"{seconds} seconds" minutes = int(seconds / 60) if minutes == 1: return "About a minute" if minutes < 60: return f"{minutes} minutes" hours = int(round(seconds / 3600)) if hours == 1: return "About an hour" if hours < 48: return f"{hours} hours" if hours < 24*7*2: return f"{int(hours/24)} days" if hours < 24*30*2: return f"{int(hours/24/7)} weeks" if hours < 24*365*2: return f"{int(hours/24/30)} months" return f"{int(hours/24/365)} years" def humanTime(t): if t == 0: return "Never" delta = datetime.now().astimezone() - t if int(delta.total_seconds()/3600/24/365) < -20: return "Forever" elif delta.total_seconds() < 0: return f"{humanDuration(-delta)} from now" return f"{humanDuration(delta)} ago" def parseArgs(): parser = argparse.ArgumentParser() parser.add_argument("-n", "--name", help="Sort by name", default=False, action="store_true") parser.add_argument("-s", "--size", help="Sort by size", default=False, action="store_true") parser.add_argument("-m", "--modified", help="Sort by modified date", default=False, action="store_true") parser.add_argument("-i", "--id", help="Sort by id", default=False, action="store_true") parser.add_argument("-a", "--ascending", help="Sort in ascending order", default=True, dest='sort_order', action="store_true") parser.add_argument("-d", "--descending", help="Sort in descending order", dest='sort_order', action="store_false") parser.add_argument("filter", nargs='*') return parser.parse_args() def showList(args): sort_key = "model" if args.name else "size" if args.size else "digest" if args.id else "modified_at" model_list = ollama.Client().list().models if args.filter: model_list = [model for model in model_list if any([model.model.startswith(f) for f in args.filter])] sorted_list = sorted(model_list, key=attrgetter(sort_key), reverse=args.sort_order) width = max([len(model.model) for model in sorted_list]) fmt = f"{{{0}:<{width}}} {{{1}:<16}}{{{2}:<11}}{{{3}}}" print(fmt.format("NAME", "ID", "SIZE", "MODIFIED")) for model in sorted_list: print(fmt.format(model.model, model.digest[:12], humanBytes(model.size), humanTime(model.modified_at))) def main(): showList(parseArgs()) if __name__ == "__main__": main() ```
Author
Owner

@agileandy commented on GitHub (Dec 6, 2024):

Thanks Rob...

A little more sophisticated than my effort which only does size!


import subprocess
import re

def run_ollama_list():
    try:
        # Run the 'ollama list' command and capture its output
        result = subprocess.run(['ollama', 'list'], capture_output=True, text=True, check=True)
        return result.stdout.strip()
    except subprocess.CalledProcessError as e:
        print(f"An error occurred while running 'ollama list': {e}")
        return None

# Function to parse the output of 'ollama list' and return a list of dictionaries
def parse_output(output):
    entries = []
    lines = output.strip().split('\n')
    
    # Skip the header line if present
    if len(lines) > 0 and 'NAME' in lines[0]:
        lines.pop(0)
    
    for line in lines:
        match = re.match(
            r'\s*(.*?)\s+(.*?)\s+(.*?GB|.*?MB)\s+(.*)',
            line
        )
        if match:
            name, id_, size_str, modified = match.groups()
            
            # Convert size to float and handle MB/GB
            if 'MB' in size_str:
                size_float = float(size_str.replace(' MB', '')) / 1024.0
            else:
                size_float = float(size_str.replace(' GB', ''))
            
            entries.append({
                'Name': name.strip(),
                'ID': id_.strip(),
                'Size': size_float,  # Convert to GB for consistent sorting
                'Modified': modified.strip()
            })
    
    return entries

# Function to format the output
def format_output(entries):
    formatted_lines = []
    header = f"{'NAME':<40} {'ID':<25} {'SIZE':<15} {'MODIFIED'}"
    separator = "=" * 96
    formatted_lines.append(header)
    formatted_lines.append(separator)
    
    for entry in entries:
        name_truncated = entry['Name'][:37] + '...' if len(entry['Name']) > 40 else entry['Name']
        size_str = f"{entry['Size']:.2f} GB" if entry['Size'] >= 1 else f"{int(entry['Size'] * 1024)} MB"
        line = (
            f"{name_truncated:<40} "
            f"{entry['ID']:<25} "
            f"{size_str:<15} "
            f"{entry['Modified']}"
        )
        formatted_lines.append(line)
    
    return "\n".join(formatted_lines)

# Function to sort entries by size
def sort_entries_by_size(entries):
    return sorted(entries, key=lambda x: x['Size'], reverse=True)  # Sort by size in descending order

# Main function
def main():
    output = run_ollama_list()
    if not output:
        return
    
    entries = parse_output(output)
    sorted_entries = sort_entries_by_size(entries)
    formatted_output = format_output(sorted_entries)
    
    print(formatted_output)

if __name__ == "__main__":
    main()
    

<!-- gh-comment-id:2521840423 --> @agileandy commented on GitHub (Dec 6, 2024): Thanks Rob... A little more sophisticated than my effort which only does size! ```python import subprocess import re def run_ollama_list(): try: # Run the 'ollama list' command and capture its output result = subprocess.run(['ollama', 'list'], capture_output=True, text=True, check=True) return result.stdout.strip() except subprocess.CalledProcessError as e: print(f"An error occurred while running 'ollama list': {e}") return None # Function to parse the output of 'ollama list' and return a list of dictionaries def parse_output(output): entries = [] lines = output.strip().split('\n') # Skip the header line if present if len(lines) > 0 and 'NAME' in lines[0]: lines.pop(0) for line in lines: match = re.match( r'\s*(.*?)\s+(.*?)\s+(.*?GB|.*?MB)\s+(.*)', line ) if match: name, id_, size_str, modified = match.groups() # Convert size to float and handle MB/GB if 'MB' in size_str: size_float = float(size_str.replace(' MB', '')) / 1024.0 else: size_float = float(size_str.replace(' GB', '')) entries.append({ 'Name': name.strip(), 'ID': id_.strip(), 'Size': size_float, # Convert to GB for consistent sorting 'Modified': modified.strip() }) return entries # Function to format the output def format_output(entries): formatted_lines = [] header = f"{'NAME':<40} {'ID':<25} {'SIZE':<15} {'MODIFIED'}" separator = "=" * 96 formatted_lines.append(header) formatted_lines.append(separator) for entry in entries: name_truncated = entry['Name'][:37] + '...' if len(entry['Name']) > 40 else entry['Name'] size_str = f"{entry['Size']:.2f} GB" if entry['Size'] >= 1 else f"{int(entry['Size'] * 1024)} MB" line = ( f"{name_truncated:<40} " f"{entry['ID']:<25} " f"{size_str:<15} " f"{entry['Modified']}" ) formatted_lines.append(line) return "\n".join(formatted_lines) # Function to sort entries by size def sort_entries_by_size(entries): return sorted(entries, key=lambda x: x['Size'], reverse=True) # Sort by size in descending order # Main function def main(): output = run_ollama_list() if not output: return entries = parse_output(output) sorted_entries = sort_entries_by_size(entries) formatted_output = format_output(sorted_entries) print(formatted_output) if __name__ == "__main__": main() ```
Author
Owner

@pdevine commented on GitHub (Dec 6, 2024):

You can sort using normal unix commands:

To sort by name:

ollama ls | sort

To sort by size:

ollama ls | awk '{printf("%s%s\t %s\n", $3, $4, $1)}' | tail -n +2 | sort -h
<!-- gh-comment-id:2521846663 --> @pdevine commented on GitHub (Dec 6, 2024): You can sort using normal unix commands: To sort by name: ``` ollama ls | sort ``` To sort by size: ``` ollama ls | awk '{printf("%s%s\t %s\n", $3, $4, $1)}' | tail -n +2 | sort -h ```
Author
Owner

@agileandy commented on GitHub (Dec 6, 2024):

@pdevine - I played around with awk for hours but couldn't get it work (I'm not a shell script expert!) so this is much appreciated.

I've just added an alias for the above.

<!-- gh-comment-id:2521958826 --> @agileandy commented on GitHub (Dec 6, 2024): @pdevine - I played around with awk for hours but couldn't get it work (I'm not a shell script expert!) so this is much appreciated. I've just added an alias for the above.
Author
Owner

@pdevine commented on GitHub (Dec 6, 2024):

A slightly more succinct version:

ollama ls | awk '{print $3$4,$1}' | sort -h

The tail -n +2 part just means skip the first line of output which will get messed up in this last version. The awk command just prints out the space delimited column ($3 is the size, $4 is the unit), and $1 is the name of the model. sort -h just means to sort by human size, and it expects there to be no space between the size and the unit.

I'll go ahead and close since there aren't any immediate plans. My hope is we can keep the CLI very unix-y, but there will be other UIs where you'll be able to do this.

<!-- gh-comment-id:2524190312 --> @pdevine commented on GitHub (Dec 6, 2024): A slightly more succinct version: ``` ollama ls | awk '{print $3$4,$1}' | sort -h ``` The `tail -n +2` part just means skip the first line of output which will get messed up in this last version. The awk command just prints out the space delimited column ($3 is the size, $4 is the unit), and $1 is the name of the model. `sort -h` just means to sort by human size, and it expects there to be no space between the size and the unit. I'll go ahead and close since there aren't any immediate plans. My hope is we can keep the CLI very unix-y, but there will be other UIs where you'll be able to do this.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#5084