Get specific rows from a file

Last update: 29 June, 2023

I wanted to update some python dependencies with pip, and I got this output when I run pip list --outdated:

$ pip list --outdated

Package    Version Latest Type
---------- ------- ------ -----
fonttools  4.33.3  4.40.0 wheel
pip        22.1    23.1.2 wheel
setuptools 58.1.0  68.0.0 wheel

And I thought: “Hm, it would be nice if I could print from the 3rd line and below, I don’t want the table header and separator in my output. This way, I can grab the package names with ease”.

You can print specific lines with awk and the NR built-in variable. NR contains the row number (aka line), which is an index that starts from 1:

$ pip list --outdated |
  awk '{ if (NR > 2) print $0 }'

fonttools  4.33.3  4.40.0 wheel
pip        22.1    23.1.2 wheel
setuptools 58.1.0  68.0.0 wheel

print $0 means print all the fields (columns) in the row. $1 refers to the first field, $2 to the second, and so on. awk is build for that kind of stuff.

And the complete command, where I upgrade the dependencies (use it at you own risk, check first which dependencies you really want to upgrade!):

$ pip list --outdated |
  awk '{ if (NR > 2) print $0 }' |
  grep -v pip |
  cut --delimiter ' '  --field 1 |
  xargs pip install --upgrade
  1. grep -v pip removes pip from the results ( -v, --invert-match select non-matching lines) because I didn’t want to upgrade pip this way (I got an error when I did ERROR: To modify pip, please run the following command:..).
  2. With cut --delimiter ' ' --field 1 I get the 1st column from the output which is the package name. The default delimiter for cut I believe is the tab character, that’s why I use a space as a delimiter.
  3. With command | xargs other-command, if the command output is, for example:
# output of "command"
1
2
3

You transform it to:

$ other-command 1 2 3

In other words, you pass each line from the output of the previous command as a positional argument to the next command (the one that xargs calls), and execute the next command (other-command in this example).

And if you want only a specific row, for example the 2nd row:

$ echo -e '3\n6\n1' | awk '{ if (NR == 2) print $0 }'

6

echo -e means enable interpretation of backslash escapes, if you don’t use it, it will print:

$  echo '3\n6\n1'

3\n6\n1

Use the paste command if you’re feeling adventurous

A less robust way would be to use the paste command with the --serial option and then the cut command:

# doesn't work pip list -o, only with echo for some reason
# probably pip list -o has some funny characters?
$ echo 'Package    Version Latest Type
---------- ------- ------ -----
fonttools  4.33.3  4.40.0 wheel
pip        22.1    23.1.2 wheel
setuptools 58.1.0  68.0.0 wheel' |
  paste --serial --delimiters '*' |
  cut -d '*' -f 3-  |
  tr '*' '\n'

# result
fonttools  4.33.3  4.40.0 wheel
pip        22.1    23.1.2 wheel
setuptools 58.1.0  68.0.0 wheel

In the example above, I print the text directly into the console with echo because pip list --outdated doesn’t really work here with this method, for some reason I don’t care to investigate.

To understand how paste works, see a note of mine on how to split even and odd lines into two columns with paste.

cut -f 3- means get from the 3 column until the last column (see man cut for more stuff the -f option can do). And, if you want a specific row, for example the 3rd:

$ pip list -o |
  paste --serial --delimiters '*' |
  cut -d '*' -f 3

I’m using ’*’ as delimiter; find a char that is not part of your data/output/files.

Other things to read

Popular

Previous/Next