I'm sure this is pretty simple but I can't seem to find a good example. I'm trying to parse /proc/1/exe to find the init system of a box. Exe is a symlink to the init system, but it has quotes in it when you stat the file, which I want to remove. Unfortunately, I've seen the regular apostrophe (U+0027) in exe and right and left single quotation marks (U+2018 and U+2019). It varies between systems. I'm not sure if that is due the shell I'm running and/or its version, if it's the stat utility, or if it's the system itself, I've just seen all three of these characters (sometimes mixed together).

Ultimately, I just want to remove all of these characters with the same sed command. So far I've only been able to do it by piping sed to another sed command.

This is the entire exe file:

$stat /proc/1/exe

  File: '/proc/1/exe' -> '/sbin/init'
  Size: 0           Blocks: 0          IO Block: 1024   symbolic link
Device: b9h/185d    Inode: 76948360    Links: 1
Access: (0777/lrwxrwxrwx)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2017-02-01 14:52:39.101744022 -0700
Modify: 2017-01-25 12:52:20.741244423 -0700
Change: 2017-01-25 12:52:20.741244423 -0700
 Birth: -

Note that in this example it is just the regular apostrophes but I've seen the other quote characters as well. So I start by getting just the content I need:

$stat /proc/1/exe | grep File: | awk -F '->' '{print $2}'

Now I want to remove the quotes and the only way I've been able to remove all the different type of quotes is by using multiple sed commands:

$stat /proc/1/exe | grep File: | awk -F '->' '{print $2}' | sed 's/\xe2\x80\x98//g' | sed 's/\xe2\x80\x99//g' | sed 's/\x27//g'

After that I just pipe it to another awk statement:

$stat /proc/1/exe | grep File: | awk -F '->' '{print $2}' | sed s'/\xe2\x80\x98//g' | sed 's/\xe2\x80\x99//g' | sed 's/\x27//g' | awk -F '/' '{print $NF}'

I'm aware of /proc/1/comm or just using an ls on /proc/1/exe. The question is how do I combine the three sed commands into one. It should read:

If a left quote or right quote or apostrophe exists, remove it. And it should do this on all occurrences.

On a side note, if somebody knows of a better command to get the init system of a box (besides /proc/1/comm as I've seen that be unreliable) or can just make these commands more efficient I'd be interested in learning it.

  • ps -p 1 -o comm= – Rui F Ribeiro Feb 2 '17 at 0:42
  • 3
    readlink /proc/1/exe – thrig Feb 2 '17 at 0:47

As others have pointed out, there are various better ways to determine the nature of PID 1. Using several invocations of grep, sed and awk in a single pipeline is almost never a good idea. If grep can't do it, use sed. If sed can't do it (easily), use awk. Hence:

stat /proc/1/exe | sed -n '/File:/{s/.*-> *[\xe2\x80\x98]//;s/[\xe2\x80\x99].*//p;q}'
  • This works well on systems with sysvinit on it. For some reason I don't get any results when /proc/1/exe links to /lib/systemd/systemd/. I'm trying to dig into that now. – Joshua Schaeffer Feb 2 '17 at 16:56
  • @JoshuaSchaeffer I find that surprising, since I wrote and tested my solution on a system using systemd. – Michael Vehrs Feb 3 '17 at 7:18
  • I found the reason, it was because the regex only looks for left and right quotes and /proc/1/exe uses regular apostrophes on the system I tested with systemd. I just added \x27 and it worked. I'm not sure why some systems I get one or the other or both, but as long as I include both in the character set it works. – Joshua Schaeffer Feb 3 '17 at 15:52

A slightly different approach can be taken here which still meets the original question of "how do you combine the three sed commands into one" and also improves the command itself. No doubt there are plenty of other approaches to solve the secondary question of how to retrieve the init system.

Instead of trying to strip out what you don't need you can capture what you do need with capture groups and you can match on multiple values using character sets. The character set is denoted by brackets "[ ]". Inside you put exactly what you want to match on an you get an implicit OR statement between them. So in order to match a left quote OR an apostrophe you use:

[\xe2\x80\x98 \x27]

The left quote is the "e28098" hex character and the apostrophe is the "27" hex character. I put a space between them just to emphasize the two separate characters, but technically this will match a left quote OR a space OR an apostrophe. Remove the space if you do not want to match on it. You can add "e28099" as well if you wanted to match the right quote.

To improve the command itself and just pull either "init" or "systemd" you can take a different approach using capture groups. Capture groups are denoted by parenthesis "( )". You can then reference that capture group. For example I ended up using this command to capture exactly what I needed:

stat /proc/1/exe | sed -rn 's/^.*File:.*->.*[\xe2\x80\x98\x27]\/.*\/(.+)[\xe2\x80\x99\x27]$/\1/p'

The capture group (the ".+" between parenthesis) captures anything after the last forward slash that is between a left quote or apostrophe and a right quote or apostrophe. It is referenced by using "\1" (as it is the first and only capture group). Sed replaces the entire line with just what is in the capture group.

  • -r is used for extended regular expressions (at least on my version of sed)
  • -n is used to suppress printing. When combined with the "/p" option it will only print lines that match the pattern, but after they have been replaced. This makes sed operate like grep.

Hopefully this helps somebody else.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.