Splitting /proc/cmdline arguments with spaces
Asked Answered
C

9

21

Most scripts that parse /proc/cmdline break it up into words and then filter out arguments with a case statement, example:

CMDLINE="quiet union=aufs wlan=FOO"
for x in $CMDLINE
do
»···case $x in
»···»···wlan=*)
»···»···echo "${x//wlan=}"
»···»···;;
»···esac
done

The problem is when the WLAN ESSID has spaces. Users expect to set wlan='FOO BAR' (like a shell variable) and then get the unexpected result of 'FOO with the above code, since the for loop splits on spaces.

Is there a better way of parsing the /proc/cmdline from a shell script falling short of almost evaling it?

Or is there some quoting tricks? I was thinking I could perhaps ask users to entity quote spaces and decode like so: /bin/busybox httpd -d "FOO%20BAR". Or is that a bad solution?

Cuneal answered 14/6, 2009 at 18:38 Comment(2)
Your shell is already parsing the command line, splitting arguments. Why do you want to "unparse" it and not just assume that the user will quote the argument properly--like it has to do with any other command? If you want to remove a file with space, you type «rm "file with space"». What if your ESSID is «essid wlan=FOO»?Enrichetta
Ok, i did the same mistake as JesperE...Enrichetta
B
13
set -- $(cat /proc/cmdline)
for x in "$@"; do
    case "$x" in
        wlan=*)
        echo "${x#wlan=}"
        ;;
    esac
done
Bustee answered 22/2, 2013 at 15:47 Comment(2)
very elegant solution!Doykos
for x; do ... defaults to the same semantics as for x in "$@"; do ... But the more explicit form is ... more explicit. Just a side note. :)Indifferent
C
5

Use xargs -n1:

[centos@centos7 ~]$ CMDLINE="quiet union=aufs wlan='FOO BAR'"
[centos@centos7 ~]$ echo $CMDLINE
quiet union=aufs wlan='FOO BAR'
[centos@centos7 ~]$ echo $CMDLINE | xargs -n1
quiet
union=aufs
wlan=FOO BAR

[centos@centos7 ~]$ xargs -n1 -a /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-3.10.0-862.14.4.el7.x86_64
root=UUID=3260cdba-e07e-408f-93b3-c4e9ff55ab10
ro
consoleblank=0
crashkernel=auto
rhgb
quiet
LANG=en_US.UTF-8
Criticaster answered 27/9, 2020 at 20:9 Comment(3)
This is perfect solution; having these one per line makes post-processing very easy.Nitrate
I've read the man page but still don't understand what -n1 does and why it works. But it does work, and I like it.Lashaunda
on RHEL 8.7 they have xargs --null - here it is: cat /proc/<pid>/cmdline | xargs --nullBaluchistan
N
4

Most commonly, \0ctal escape sequences are used when spaces are unacceptable.

In Bash, printf can be used to unescape them, e.g.

CMDLINE='quiet union=aufs wlan=FOO\040BAR'
for x in $CMDLINE; do
    [[ $x = wlan=* ]] || continue
   printf '%b\n' "${x#wlan=}"
done
Negligent answered 17/6, 2009 at 17:47 Comment(5)
I prefer Web style entity quoting.Cuneal
Octal escapes are more common (and traditional) in UNIX environments. This is how to add spaces to mount paths in /etc/fstab, for example.Negligent
Since this a parameter for a "Web product" Webconverger, where other parameters like homepage webconverger.org/boot will also be URL encoded, I think my initial choice is best.Cuneal
Then you have to use some kind of text processing tool: either sed/perl or a good shell like new bash.Enrichetta
Well, you could probably use hex escapes instead, which at least have the same numbers as URL escapes. The example would become wlan=FOO\x20BAR. I think printf will still unescape it.Cryology
M
4

Since you want the shell to parse the /proc/cmdline contents, it's hard to avoid eval'ing it.

#!/bin/bash
eval "kernel_args=( $(cat /proc/cmdline) )"
for arg in "${kernel_args[@]}" ; do
    case "${arg}" in
        wlan=*)
            echo "${arg#wlan=}"
            ;;
    esac
done

This is obviously dangerous though as it would blindly run anything that was specified on the kernel command-line like quiet union=aufs wlan=FOO ) ; touch EVIL ; q=( q.

Escaping spaces (\x20) sounds like the most straightforward and safe way.

A heavy alternative is to use some parser, which understand shell-like syntax. In this case, you may not even need the shell anymore. For example, with python:

$ cat /proc/cmdline
quiet union=aufs wlan='FOO BAR' key="val with space" ) ; touch EVIL ; q=( q
$ python -c 'import shlex; print shlex.split(None)' < /proc/cmdline
['quiet', 'union=aufs', 'wlan=FOO BAR', 'key=val with space', ')', ';', 'touch', 'EVIL', ';', 'q=(', 'q']
Mohsen answered 16/3, 2017 at 21:21 Comment(0)
B
4

cat /proc/PID/cmdline | tr \\0 \_

just put in a space instead of _

And the number of your process id instead of PID

Baluchistan answered 19/7, 2023 at 11:2 Comment(0)
S
2

You could do something like the following using bash, which would turn those arguments in to variables like $cmdline_union and $cmdline_wlan:

bash -c "for i in $(cat /proc/cmdline); do printf \"cmdline_%q\n\" \"\$i\"; done" | grep = > /tmp/cmdline.sh
. /tmp/cmdline.sh

Then you would quote and/or escape things just like you would in a normal shell.

Scudo answered 2/6, 2011 at 2:59 Comment(0)
E
1

In posh:

$ f() { echo $1 - $3 - $2 - $4 
> }
$ a="quiet union=aufs wlan=FOO"
$ f $a
quiet - wlan=FOO - union=aufs -

You can define a function and give your $CMDLINE unquoted as an argument to the function. Then you'll invoke shell's parsing mechanisms. Note, that you should test this on the shell it will be working in -- zsh does some funny things with quoting ;-).

Then you can just tell the user to do quoting like in shell:

#!/bin/posh
CMDLINE="quiet union=aufs wlan=FOO"
f() {
        while test x"$1" != x 
        do      
                case $1 in
                        union=*)        echo ${1##union=}; shift;;
                        *)              shift;; 
                esac    
        done    
}       
f $CMDLINE

(posh - Policy-compliant Ordinary SHell, a shell stripped of any features beyond standard POSIX)

Enrichetta answered 17/6, 2009 at 11:0 Comment(3)
Because you can eat more arguments (with $2, $3... and more shifts). I agree that this not so useful for x=y arguments; more for -x y. But this solution is more versatile and a bit more idiomatic (seeing it very often in shell scripts).Enrichetta
So how would users quote an essid like 'foo bar'? wlan=foo\040bar like the other answer? I honestly think URL encoding "%20" is easier for the average user than shell octal encodings.Cuneal
In this case, a shell-like escaping will apply: CMDLINE="quiet wlan='foo bar bar union=of=consumers and-yet-another word' union=aufs". But... now I think that if this is meant also to be kernel parameters, you should be rather using octal. Kernel does not do shell expansion and might be confused seeing above wlan id.Enrichetta
P
1

I wanted to add comment explaining the xargs -n1 answer but my reputation is <50 so I'm abusing "Your Answer".

xargs(1)
...
       This manual page documents the GNU version of xargs.  xargs reads items from the standard input, delimited by blanks (which  can
       be  protected  with  double or single quotes or a backslash) or newlines, and executes the command (default is /bin/echo) one or
       more times with any initial-arguments followed by items read from standard input.  Blank lines on the  standard  input  are  ig‐
       nored.
...
       --max-args=max-args
       -n max-args
              Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see the -s  op‐
              tion) is exceeded, unless the -x option is given, in which case xargs will exit.
...

So xargs -n1 means:

  • pass each argument to /bin/echo as no command specified
  • process one argument at a time

My use case was to provide some share credentials on the kernel command line for a pxe environment so /proc/cmdline looks like:

vmlinuz initrd=initrd.img file=/cdrom/preseed/ubuntu.seed boot=casper noprompt ip=dhcp console=tty1 console=ttyS1 consoleblank=0 netboot=url url=http://192.168.1.1/some-ubuntu-derived.iso smb.path=smb://192.168.2.2/a/share smb.username='name with space' smb.password=password smb.domain='DOMAIN.CORP'

So I iterated over the arguments in bash:

while read -r -u 3 karg ; do
    case "${karg}" in
    smb.*)
        echo "${karg}"
        ;;
    *)
       : # no-op - not for me
       ;;
    esac
done 3< <(xargs -n1 < /proc/cmdline)
Prothorax answered 15/3, 2023 at 15:12 Comment(0)
S
-1

Found here a nice way to do it with awk, unfortunately it will work only with doublequotes:

# Replace spaces outside double quotes with newlines
args=`cat /proc/cmdline | tr -d '\n' | awk 'BEGIN {RS="\"";ORS="\"" }{if (NR%2==1){gsub(/ /,"\n",$0);print $0} else {print $0}}'`

IFS='                                                                           
'                                                                               
for line in $args; do                                                           
     key=${line%%=*}                                                            
     value=${line#*=}                                                           

     value=`echo $value | sed -e 's/^"//' -e 's/"$//'`                          

     printf "%20s = %s\n" "$key" "$value"                                       

done
Scarab answered 26/4, 2013 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.