I have a list of packages I want to deinstall. The deinstaller program pkg_deinstall doesn't take a list of packages as parameter to deinstall. How would I deinstall from the list (like a foreach loop)?

[root@fbsd01 /usr/ports/editors/vim]# pkg_info | grep proto| sed 's/\([a-z0-9]*\).*/\1/'

I would think a command similar to this would work, but I have to pass the list as a parameter and not a stream: pkg_info | grep proto| sed 's/\([a-z0-9]*\).*/\1/' | head -n 1 pkg_deinstall

If you could give me some clues as to what program and syntax to use, that would be helpful. I know since it's unique you might have a hard time coming up with an exact answer. If my question is too complicated, maybe someone could show me how to do actions on an ls of files in a dir.


For the most basic 'foreach' there is xargs, it reads some parameters and append them to some other command line. For example you may try:

> cut -d: -f1 /etc/passwd | xargs -n1 echo user:

Note that you should pass a -d'\n' argument to xargs in the case usernames could contain space characters. To append all¹ parameters to the same command use:

> cut -d: -f1 /etc/passwd | xargs echo users:

For more general loops, you can use this:

> cut -d: -f1 /etc/passwd | while read i; do echo $i; done

This time, $i can be placed anywhere in a sequence of one or more commands. Inputs are read one line at a time, and stored in the variable i. It is also possible to split² the line and store it in several variables:

> cut -d: -f1,6 /etc/passwd --output-delimiter=' ' | \
  while read i j; do echo home directory of user $i is $j; done

Note that for loops over raw filenames, the following is better (see comments):

> for i in *.txt; do echo file: $i; done

1. More precisely: as many as the system will allow.
2. According to the IFS variable

  • 1
    In your case, since the pkg_deinstall command allows multiple arguments, a simple pkg_info | grep proto | sed 's/([a-z0-9])./\1/' | xargs pkg_deinstall should be enough. Jul 29 '11 at 17:16
  • Yes, this is what I needed. I got it working. Thank you very much!
    – EhevuTov
    Jul 29 '11 at 17:18
  • 2
    Parsing the output of ls to act on files is to be avoided. Use shell globing such as for file in *; do [...] done or find -exec instead. In this OP's question they already have a list of packages generated, so piping that into a while read loop is probably fine, but for file names it is not!
    – Caleb
    Jul 29 '11 at 21:51
  • @Caleb it should be ok as long as I do a regex with sed right?
    – EhevuTov
    Jul 29 '11 at 22:46
  • @Stephane: Why don't you make your comment your answer, avoiding the problematic ls? Jul 30 '11 at 3:16

There are two main ways to convert an output stream into an argument list. The quick (and sometimes dirty) way to do this is with xargs. By default it accepts a standard input stream and runs a command with each line of the input as a separate argument. You should always be careful about the format of the input because spaces and other characters are easy to misinturpret when passed as arguments. Package names, however, are relativly safe. They shouldn't include shell wildcard characters or newlines or anything crazy, so here's what it would look like for your command:[3]

$ pkg_info | grep proto | sed 's/\([a-z0-9]*\).*/\1/' | xargs pkg_deinstall -n

A frequent case is that you actually need to run the command once for every line being processed. In this case the -n argument comes in handy. You can use -n1 to run them one at a time, or even -n10 to run them in batches of ten.

$ [pipline] | xargs -n1 pkg_deinstall -n

Now let's say you have a need to add another argument after the automaticly generated once. In this case you can use '-I' to specify a string to replace with the auto-generated arguments. This will also allow you to do things like quote the argument if necessary. You can then format where the arguments will get placed like this:[1][2]

$ [pipline] | xargs -n1 -I{} pkg_deinstall -n "{}" --trailing-argument

If you wanted more control or to run multiple commands for each line of input, your next option is to use a loop like this:

$ [pipline] | while read line; do
      echo "Uninstalling $line..."
      pkg_deinstall -n "$line"

Note that the pkg_deinstall automatically uses the expression as a partial match glob, so be careful about what you pass to be uninstalled. Passing the base name of some system component will uninstall not only that component, but everything that has a name like it or depends on it! This could be catastrophic, so always carefully check your package set. You can use -n to do a dry run that shows what would be done without actually doing it[3] and/or -i to run each step interactively.

[1] I used {} for because find and other similar exec functions use this format, but any unique string will do.
[2] The pkg_deinstall command doesn't have any arguments that would come after the patern, so this is an example only!
[3] I added -n to all my examples so that if you copy and paste the code it won't actually do anything, just show you a test run.


Not to take away from the other answers... This is just one more way to do it:

for pkg in $( pkg_info | grep proto| sed 's/\([a-z0-9]*\).*/\1/' ) ; do
    pkg_deinstall $pkg

Quoting is a nightmare in example, so I personally tend to use | while read foo ... more often than not.

  • Make that while IFS= read -r foo; do … (-r to disable the special treatment of backslashes and IFS= to avoid stripping initial whitespace). Jul 31 '11 at 22:15

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.