[PATCH v3 3/3] psql: Fix counting of header and footer lines in pager setup

Erik Wienhold <ewie@ewie.name>

From: Erik Wienhold <ewie@ewie.name>
To:
Date: 2025-09-09T00:36:49Z
Lists: pgsql-hackers
* The table header only produces extra lines when printed in normal
  mode.  So don't count those lines in tuples_only mode or expanded
  mode.

* Count the lines of the table title, if present.

* Count all footer lines instead of treating each footer as a single
  line.
---
 src/fe_utils/print.c | 39 +++++++++++++++++++++++++++------------
 1 file changed, 27 insertions(+), 12 deletions(-)

diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index 82f4fc9af20..94043fae663 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -3394,15 +3394,8 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 				   NULL, &nl_lines, NULL);
 
 		header_height[i] = nl_lines;
-
-		if (nl_lines > max_lines)
-			max_lines = nl_lines;
 	}
 
-	/* Add height of tallest header column */
-	lines += max_lines;
-	max_lines = 0;
-
 	/* Scan all cells to count their lines */
 	for (i = 0, cell = cont->cells; *cell; cell++)
 	{
@@ -3449,12 +3442,34 @@ IsPagerNeeded(const printTableContent *cont, unsigned int *width_wrap,
 	{
 		printTableFooter *f;
 
-		/*
-		 * FIXME -- this is slightly bogus: it counts the number of
-		 * footers, not the number of lines in them.
-		 */
+		if (cont->title)
+		{
+			pg_wcssize((const unsigned char *) cont->title, strlen(cont->title),
+					   cont->opt->encoding, NULL, &nl_lines, NULL);
+			lines += nl_lines;
+		}
+
+		if (!expanded)
+		{
+			/* Find the tallest header column */
+			for (i = 0; i < cont->ncolumns; i++)
+			{
+				if (header_height[i] > max_lines)
+					max_lines = header_height[i];
+			}
+
+			/* Add height of tallest header column */
+			lines += max_lines;
+			max_lines = 0;
+		}
+
+		/* Count all footer lines */
 		for (f = cont->footers; f; f = f->next)
-			lines++;
+		{
+			pg_wcssize((const unsigned char *) f->data, strlen(f->data),
+					   cont->opt->encoding, NULL, &nl_lines, NULL);
+			lines += nl_lines;
+		}
 	}
 
 	*fout = PageOutput(lines, cont->opt);
-- 
2.51.0


--h4pjkjw5wkhovhea
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="test-psql-pager.py"

# This script runs different test cases through psql to find the maximum number
# of lines that still trigger psql to use the pager.
#
# Termios is used to set the terminal size.
#
# Use environment variable PATH to select the psql binary to test.  Use libpq
# environment variables to select a database for testing in which this script
# can run the test setup.

import argparse
import os
import os.path
import subprocess
import sys
import tempfile
import time
import termios
import typing


def run_tests(outfile):
    # Prepare database schema
    proc = subprocess.run(['psql', '-X'], text=True, input=r'''
\set ON_ERROR_STOP on
BEGIN;

CREATE OR REPLACE FUNCTION generate_lines(n int)
    RETURNS TABLE (lines text)
    LANGUAGE sql
    AS $$ SELECT string_agg(s::text, e'\n') FROM generate_series(1, n) s $$;

-- This creates a view name and column name with 24 line breaks when truncated
-- to the default NAMEDATALEN (63 = 9*2 + 15*3)
SELECT format('CREATE OR REPLACE VIEW %I (c) AS SELECT null;'
              'CREATE OR REPLACE VIEW nl_column (%1$I) AS SELECT null;',
              string_agg(s::text, e'\n'))
FROM generate_series(1, current_setting('max_identifier_length')::int) s
\gexec

COMMIT;
''').check_returncode()

    testcases = []
    for cmd in commands:
        # Repeat each command with every combination of flags that affect the
        # number of output lines.
        for flags in range(1 << 3):
            testcases.append(Testcase(
                cmd=cmd,
                unaligned=flags & 1,
                tuples_only=flags & (1 << 1),
                expanded=flags & (1 << 2),
            ))

    # Remember current term size
    save_term_size = termios.tcgetwinsize(sys.stdout.fileno())

    max_paged_lines = []
    for tc in testcases:
        max_paged_lines.append(find_max_paged_lines(tc.psql_args()))

    # Restore term size
    termios.tcsetwinsize(sys.stdout.fileno(), save_term_size)

    # Print the testcase results
    for tc, lines in zip(testcases, max_paged_lines):
        flags = ''
        flags += 'A' if tc.unaligned else '-'
        flags += 't' if tc.tuples_only else '-'
        flags += 'x' if tc.expanded else '-'
        # Make sure we get one output line per testcase
        cmd = tc.cmd.replace('\n', r'\n')
        print(f"{lines:2}  {flags}  {cmd}", file=outfile)


def run_psql_with_pager(args):
    with tempfile.NamedTemporaryFile() as tmp:
        mtime_before = os.stat(tmp.name).st_mtime_ns
        env = {
            # Inherit environment variables (especially PATH and libpq-specific
            # ones).
            **os.environ,
            # Set PAGER so that we can tell from the temp file's mtime that the
            # pager was triggered.
            'PAGER': f'touch {tmp.name}',
        }
        proc = subprocess.run(['psql', '-X', *args], env=env)
        if proc.returncode:
            return None
        mtime_after = os.stat(tmp.name).st_mtime_ns
    pager_used = mtime_after > mtime_before
    return pager_used


def find_max_paged_lines(psql_args):
    # Binary search the maximum number of lines at which psql still triggers
    # the pager.
    min_lines = 1
    max_lines = 100
    cols = 100  # sufficient for our test cases
    while min_lines <= max_lines:
        lines = min_lines + (max_lines - min_lines) // 2
        termios.tcsetwinsize(sys.stdout.fileno(), (lines, cols))
        pager_used = run_psql_with_pager(psql_args)
        if pager_used is None:
            return -1
        if pager_used:
            min_lines = lines + 1
        else:
            max_lines = lines - 1
    return max_lines


class Testcase(typing.NamedTuple):
    cmd: str
    unaligned: bool
    tuples_only: bool
    expanded: bool

    def psql_args(self):
        args = ['-c', self.cmd]
        if self.unaligned:
            args.append('-A')
        if self.tuples_only:
            args.append('-t')
        if self.expanded:
            args.append('-x')
        return args


# The SQL and meta commands we will be testing
commands = [
    'SELECT * FROM generate_lines(25)',
    'SELECT * FROM nl_column',
    '\\d nl_column',
    '\\d+ nl_column',
    '\\d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"',
    '\\d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"',
]

parser = argparse.ArgumentParser()
parser.add_argument('expectfile', metavar='FILE', help="file with expected test results")
args = parser.parse_args()

basename, _ = os.path.splitext(args.expectfile)
outfile = basename + '.tmp'
difffile = basename + '.diff'

with open(outfile, 'w') as fp:
    run_tests(fp)

with open(difffile, 'w') as fp:
    if os.path.isfile(args.expectfile):
        srcfile = args.expectfile
    else:
        srcfile = os.devnull
    proc = subprocess.run(['diff', '-u', srcfile, outfile], stdout=fp)

if proc.returncode:
    print()
    print("Test output does not match the expected output.")
    print(f'The differences can be viewed in file "{difffile}".')
    exit(1)

--h4pjkjw5wkhovhea
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="0-master.out"

27  ---  SELECT * FROM generate_lines(25)
 2  A--  SELECT * FROM generate_lines(25)
27  -t-  SELECT * FROM generate_lines(25)
 2  At-  SELECT * FROM generate_lines(25)
 2  --x  SELECT * FROM generate_lines(25)
 2  A-x  SELECT * FROM generate_lines(25)
 2  -tx  SELECT * FROM generate_lines(25)
 2  Atx  SELECT * FROM generate_lines(25)
27  ---  SELECT * FROM nl_column
 2  A--  SELECT * FROM nl_column
27  -t-  SELECT * FROM nl_column
 2  At-  SELECT * FROM nl_column
 2  --x  SELECT * FROM nl_column
 2  A-x  SELECT * FROM nl_column
 2  -tx  SELECT * FROM nl_column
 2  Atx  SELECT * FROM nl_column
27  ---  \d nl_column
 2  A--  \d nl_column
27  -t-  \d nl_column
 2  At-  \d nl_column
27  --x  \d nl_column
 2  A-x  \d nl_column
27  -tx  \d nl_column
 2  Atx  \d nl_column
29  ---  \d+ nl_column
 4  A--  \d+ nl_column
27  -t-  \d+ nl_column
 2  At-  \d+ nl_column
29  --x  \d+ nl_column
 4  A-x  \d+ nl_column
27  -tx  \d+ nl_column
 2  Atx  \d+ nl_column
 3  ---  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  A--  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  At-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  --x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  A-x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  Atx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  ---  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 4  A--  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  At-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  --x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 4  A-x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  Atx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"

--h4pjkjw5wkhovhea
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="1-refactor.out"

27  ---  SELECT * FROM generate_lines(25)
27  A--  SELECT * FROM generate_lines(25)
27  -t-  SELECT * FROM generate_lines(25)
27  At-  SELECT * FROM generate_lines(25)
27  --x  SELECT * FROM generate_lines(25)
27  A-x  SELECT * FROM generate_lines(25)
27  -tx  SELECT * FROM generate_lines(25)
27  Atx  SELECT * FROM generate_lines(25)
27  ---  SELECT * FROM nl_column
27  A--  SELECT * FROM nl_column
27  -t-  SELECT * FROM nl_column
27  At-  SELECT * FROM nl_column
27  --x  SELECT * FROM nl_column
27  A-x  SELECT * FROM nl_column
27  -tx  SELECT * FROM nl_column
27  Atx  SELECT * FROM nl_column
27  ---  \d nl_column
27  A--  \d nl_column
27  -t-  \d nl_column
27  At-  \d nl_column
27  --x  \d nl_column
27  A-x  \d nl_column
27  -tx  \d nl_column
27  Atx  \d nl_column
29  ---  \d+ nl_column
29  A--  \d+ nl_column
27  -t-  \d+ nl_column
27  At-  \d+ nl_column
29  --x  \d+ nl_column
29  A-x  \d+ nl_column
27  -tx  \d+ nl_column
27  Atx  \d+ nl_column
 3  ---  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  A--  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  At-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  --x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  A-x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  Atx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  ---  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  A--  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  At-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  --x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  A-x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  Atx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"

--h4pjkjw5wkhovhea
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="2-expanded.out"

27  ---  SELECT * FROM generate_lines(25)
27  A--  SELECT * FROM generate_lines(25)
27  -t-  SELECT * FROM generate_lines(25)
27  At-  SELECT * FROM generate_lines(25)
27  --x  SELECT * FROM generate_lines(25)
27  A-x  SELECT * FROM generate_lines(25)
27  -tx  SELECT * FROM generate_lines(25)
27  Atx  SELECT * FROM generate_lines(25)
27  ---  SELECT * FROM nl_column
27  A--  SELECT * FROM nl_column
27  -t-  SELECT * FROM nl_column
27  At-  SELECT * FROM nl_column
51  --x  SELECT * FROM nl_column
51  A-x  SELECT * FROM nl_column
51  -tx  SELECT * FROM nl_column
51  Atx  SELECT * FROM nl_column
27  ---  \d nl_column
27  A--  \d nl_column
27  -t-  \d nl_column
27  At-  \d nl_column
27  --x  \d nl_column
27  A-x  \d nl_column
27  -tx  \d nl_column
27  Atx  \d nl_column
29  ---  \d+ nl_column
29  A--  \d+ nl_column
27  -t-  \d+ nl_column
27  At-  \d+ nl_column
29  --x  \d+ nl_column
29  A-x  \d+ nl_column
27  -tx  \d+ nl_column
27  Atx  \d+ nl_column
 3  ---  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  A--  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  At-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  --x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  A-x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  Atx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  ---  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  A--  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -t-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  At-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  --x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 5  A-x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  -tx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 3  Atx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"

--h4pjkjw5wkhovhea
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="3-header-footer.out"

27  ---  SELECT * FROM generate_lines(25)
27  A--  SELECT * FROM generate_lines(25)
26  -t-  SELECT * FROM generate_lines(25)
26  At-  SELECT * FROM generate_lines(25)
26  --x  SELECT * FROM generate_lines(25)
26  A-x  SELECT * FROM generate_lines(25)
26  -tx  SELECT * FROM generate_lines(25)
26  Atx  SELECT * FROM generate_lines(25)
27  ---  SELECT * FROM nl_column
27  A--  SELECT * FROM nl_column
 2  -t-  SELECT * FROM nl_column
 2  At-  SELECT * FROM nl_column
26  --x  SELECT * FROM nl_column
26  A-x  SELECT * FROM nl_column
26  -tx  SELECT * FROM nl_column
26  Atx  SELECT * FROM nl_column
28  ---  \d nl_column
28  A--  \d nl_column
26  -t-  \d nl_column
26  At-  \d nl_column
28  --x  \d nl_column
28  A-x  \d nl_column
26  -tx  \d nl_column
26  Atx  \d nl_column
54  ---  \d+ nl_column
54  A--  \d+ nl_column
26  -t-  \d+ nl_column
26  At-  \d+ nl_column
54  --x  \d+ nl_column
54  A-x  \d+ nl_column
26  -tx  \d+ nl_column
26  Atx  \d+ nl_column
28  ---  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
28  A--  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  -t-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  At-  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
28  --x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
28  A-x  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  -tx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  Atx  \d "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
30  ---  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
30  A--  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  -t-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  At-  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
30  --x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
30  A-x  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  -tx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"
 2  Atx  \d+ "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n"

--h4pjkjw5wkhovhea--