4

I have a list of sound sources that I'm processing with a script. An example would be:

alsa_input.usb-AVEO_Technology_Corp._USB2.0_Camera-02-Camera.analog-mono
alsa_input.pci-0000_00_14.2.analog-stereo
alsa_input.usb-Plantronics_Plantronics_GameCom_780-00-P780.analog-stereo

I'd like to sort them by substrings that are in arbitrary positions.

For example I'd like sort --by usb file.txt to put the USB devices first (while otherwise preserving their order):

alsa_input.usb-AVEO_Technology_Corp._USB2.0_Camera-02-Camera.analog-mono
alsa_input.usb-Plantronics_Plantronics_GameCom_780-00-P780.analog-stereo
alsa_input.pci-0000_00_14.2.analog-stereo

And I'd like to be able to be able to specify multiple substrings to get finer grain priority. So sort --by Platronics --by usb file.txt would put any line containing "Platronics" first, followed by the lines containing "usb", followed by the rest of the lines.

Can I accomplish this with any sort of command line utility?

3
  • You mean you want the lines containing usb moved to the top, not sort them? May 22, 2015 at 19:58
  • Move lines with particular keywords to the top by order of priority of the keyword. May 22, 2015 at 20:15
  • 1
    Sounds like G-Man nailed it then. May 22, 2015 at 20:30

4 Answers 4

5

It sounds like you want a scoring system.  Write a script to assign a score to each line, indicating how early in the output you want to see it.  awk seems well suited to this job.  For your example:

#!/bin/sh
awk '{score=0}
    /usb/ {score=1}
    /Plantronics/ {score=2}
    {print score, NR, $0}' "$@"

This assigns a score of 0 to every line by default, and then overrides it with a 1 if the line contains usb and 2 if the line contains Plantronics.  I have placed the usb and Plantronics statements in that order so, if a line contains both strings, the final value will be 2.  Then

./score file.txt | sort -k1nr -k2n | cut -d" " -f3-

(where score is the name of the script).  sort -k1nr means sort based on the first field (the score), treating it as a number and sorting higher values first (because the score script assigned high scores to the lines you’re most interested in).  -k2n means, for lines that have the same value in the first field, sort by the second field, as a number in normal, ascending order.  The second field is NR, the record number (a.k.a. line number).  This ensures that lines with the same score (e.g., those that contain usb but not Plantronics) come out in their original order.  If you don’t care about that, delete the NR, from the print statement, delete the -k2n from the sort command, and change the -f3- to -f2-.  (Actually, sort may preserve order like that by default, so you might not need that at all.)  Of course the cut -d" " -f3- strips off the numbers that the score script prepended to the data.

If you don’t fully understand how this is working, try running

./score file.txt

and

./score file.txt | sort -k1nr -k2n

This approach is quite flexible.  For example, the above code will produce, in order,

  • all lines containing Plantronics,
  • all lines containing usb (but not Plantronics), and
  • all lines containing neither of the above,

with each group sorted in order of appearance in the input file.  But, by changing the score script as follows,

#!/bin/sh
awk '{score=0}
    /usb/ {score+=1}
    /Plantronics/ {score+=2}
    {print score, NR, $0}' "$@"

we can assign a score of 3 to lines that contain both strings, so now we have

  • all lines containing Plantronics and usb, followed by
  • all lines containing Plantronics (but not usb), followed by
  • all lines containing usb (but not Plantronics), and then
  • all lines containing neither of the above.
2

With sqlite and ORDER BY clause:

$sqlite3 <<\EOT
CREATE TABLE file(line);
.import file.txt file
SELECT * FROM file
ORDER BY
CASE
    WHEN line LIKE '%USB%' THEN 0
    WHEN line LIKE '%Realtek%' THEN 1
    ELSE 3
END;
EOT
00:10.0 USB controller: VIA Technologies, Inc. VT82xxxxx UHCI USB 1.1 Controller (rev 81)
00:10.1 USB controller: VIA Technologies, Inc. VT82xxxxx UHCI USB 1.1 Controller (rev 81)
00:10.2 USB controller: VIA Technologies, Inc. VT82xxxxx UHCI USB 1.1 Controller (rev 81)
00:10.3 USB controller: VIA Technologies, Inc. VT82xxxxx UHCI USB 1.1 Controller (rev 81)
00:10.4 USB controller: VIA Technologies, Inc. USB 2.0 (rev 86)
00:05.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 10)
00:06.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 10)
00:07.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 10)
00:00.0 Host bridge: VIA Technologies, Inc. CN700/VN800/P4M800CE/Pro Host Bridge
00:00.1 Host bridge: VIA Technologies, Inc. CN700/VN800/P4M800CE/Pro Host Bridge
00:00.2 Host bridge: VIA Technologies, Inc. CN700/VN800/P4M800CE/Pro Host Bridge
00:00.3 Host bridge: VIA Technologies, Inc. PT890 Host Bridge
00:00.4 Host bridge: VIA Technologies, Inc. CN700/VN800/P4M800CE/Pro Host Bridge
00:00.7 Host bridge: VIA Technologies, Inc. CN700/VN800/P4M800CE/Pro Host Bridge
00:01.0 PCI bridge: VIA Technologies, Inc. VT8237/VX700 PCI Bridge
00:0f.0 IDE interface: VIA Technologies, Inc. VIA VT6420 SATA RAID Controller (rev 80)
00:0f.1 IDE interface: VIA Technologies, Inc. VT82C586A/B/VT82C686/A/B/VT823x/A/C PIPC Bus Master IDE (rev 06)
00:11.0 ISA bridge: VIA Technologies, Inc. VT8237 ISA bridge [KT600/K8T800/K8T890 South]
01:00.0 VGA compatible controller: VIA Technologies, Inc. CN700/P4M800 Pro/P4M800 CE/VN800 Graphics [S3 UniChrome Pro] (rev 01)
2

What you're doing isn't really sorting, but selecting. So what you'd want to do is first select all the lines that contain "Platronics", then all the lines that contain "usb", and so on, and finally all the lines that don't match anything.

I don't know of a command that does this in one go, but you can write a little script using python that does what you want:

import sys

substrings = sys.argv[1:]
lines = sys.stdin.readlines()

# for each substring, print matching lines
for substring in substrings:
    i=0
    while i<len(lines):
        if substring in lines[i]:
            # match: print and remove
            sys.stdout.write(lines[i])
            del lines[i]
        else:
            # no match: try next line
            i += 1

# finally, print all lines that weren't matched
for line in lines:
    sys.stdout.write(line)

It does what you want:

$ lspci | python substrings.py "USB controller" "PCI bridge"
00:1a.0 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #4 (rev 02)
00:1a.1 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #5 (rev 02)
00:1a.2 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #6 (rev 02)
00:1a.7 USB controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #2 (rev 02)
00:1d.0 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #1 (rev 02)
00:1d.1 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #2 (rev 02)
00:1d.2 USB controller: Intel Corporation 82801I (ICH9 Family) USB UHCI Controller #3 (rev 02)
00:1d.7 USB controller: Intel Corporation 82801I (ICH9 Family) USB2 EHCI Controller #1 (rev 02)
00:1c.0 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 1 (rev 02)
00:1c.1 PCI bridge: Intel Corporation 82801I (ICH9 Family) PCI Express Port 2 (rev 02)
00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev 92)
00:00.0 Host bridge: Intel Corporation 82Q33 Express DRAM Controller (rev 02)
00:02.0 VGA compatible controller: Intel Corporation 82Q33 Express Integrated Graphics Controller (rev 02)
00:02.1 Display controller: Intel Corporation 82Q33 Express Integrated Graphics Controller (rev 02)
00:03.0 Communication controller: Intel Corporation 82Q33 Express MEI Controller (rev 02)
00:19.0 Ethernet controller: Intel Corporation 82566DM-2 Gigabit Network Connection (rev 02)
00:1b.0 Audio device: Intel Corporation 82801I (ICH9 Family) HD Audio Controller (rev 02)
00:1f.0 ISA bridge: Intel Corporation 82801IB (ICH9) LPC Interface Controller (rev 02)
00:1f.2 IDE interface: Intel Corporation 82801IB (ICH9) 2 port SATA Controller [IDE mode] (rev 02)
00:1f.5 IDE interface: Intel Corporation 82801I (ICH9 Family) 2 port SATA Controller [IDE mode] (rev 02)
3
  • 1
    Note that, in the first (grep-based) approach, lines containing both "Platronics" and "usb" will be displayed twice: once along with all the other lines containing "Platronics", and once along with all the other lines containing "usb". May 22, 2015 at 20:10
  • Printing lines twice is not desirable for me. May 22, 2015 at 20:16
  • Yes, that's a bug. I'll remove it from the answer for now.
    – marinus
    May 22, 2015 at 20:18
1

cat file:

22
z
there's
a
moose
loose
in
the
hoose

eg. Set args to: loose moose - read by script as "$@" You can use as many as you like.

by=( "$@" )
nl file | sed -nf <(for ((i=0;i<${#by[@]};i++)) ;do 
                      echo "s/.*${by[i]}.*/$i\t&/; t p"
                    done; echo "s/^/$i\t&/; :p p") |
            sort -nk1 -nk2 | cut -f3-

Output:

loose
moose
22
z
there's
a
in
the
hoose

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .