21

Here's what I have so far:

#!/bin/bash
while read line; do
        DB=$(echo $line | cut -f1)
        USER=$(echo $line | cut -f2)
        PASS=$(echo $line | cut -f3)
        echo DB=$DB USER=$USER PASS=$PASS
done < users.txt

And a sample of the input file:

drupal_1    drupal1 tmmjXSWL
drupal_2    drupal2 FHiJSYHM
drupal_3    drupal3 b7bFNj06
drupal_4    drupal4 0AaV62EL

And output from the script:

DB=drupal_1 drupal1 tmmjXSWL USER=drupal_1 drupal1 tmmjXSWL PASS=drupal_1 drupal1 tmmjXSWL
DB=drupal_2 drupal2 FHiJSYHM USER=drupal_2 drupal2 FHiJSYHM PASS=drupal_2 drupal2 FHiJSYHM
DB=drupal_3 drupal3 b7bFNj06 USER=drupal_3 drupal3 b7bFNj06 PASS=drupal_3 drupal3 b7bFNj06

For some reason, each variable is set to the whole line. When I use echo users.txt | cut -f1 at the command line, it returns the token just fine.

2 Answers 2

25

The problem is in the command echo $line. Since there are no quotes around $line, the shell performs word splitting on it, then interprets each word as a globbing pattern. Try it out with

a='*.txt foo'
ls $a

In your case, the tabs separate words, which then become separate arguments to echo. The echo command prints its arguments separated by a space. So cut ends up receiving space-delimited fields instead of tab-delimited fields.

Always put double quotes around variable substitutions $foo and command substitutions $(foo) (unless you understand why you need to leave them out and why it's ok to do so). echo "$line" would work here, but it's a complicated way of doing what you propose.

Keeping your approach of parsing in the shell, you can make the read command parse the input into fields.

while read DB USER PASS; do
  echo "DB=$DB USER=$USER PASS=$PASS"
done <users.txt

read splits fields separated by characters in the value of the IFS variable, which consists of a space and a tab (plus a newline, which can't occur inside a line) by default. To split only at tabs, set IFS to a single tab first. Note that leading and trailing tabs are ignored and consecutive tabs count as a single one.

read treats \ as a special character: a backslash followed by a newline are ignored; \\ becomes a single backslash. If you want to avoid this behavior, pass the -r option.

1
  • 19
    +1. To set IFS to a tab char in bash: IFS=$'\t' Jun 21, 2012 at 0:52
6

How about this?

$ awk '{print "DB="$1"\tUSER="$2"\tPASS="$3}' users.txt
DB=drupal_1 USER=drupal1    PASS=tmmjXSWL
DB=drupal_2 USER=drupal2    PASS=FHiJSYHM
DB=drupal_3 USER=drupal3    PASS=b7bFNj06
DB=drupal_4 USER=drupal4    PASS=0AaV62EL

I can't tell if you have a problem to solve, or are wondering about a more theoretical issue.

2
  • Thanks, that works. I'm trying to write a script to create 40+ Drupal installations on a server for a class I'm teaching. I have scripts to setup the DBs and files, this is a piece of the script that will put it all together. The problem with my code was that the $line turned tabs into spaces as it parsed. $(echo $line | cut -d" " -f1) also works.
    – mortona42
    Jun 20, 2012 at 23:34
  • fails for me if any of the fields contain spaces
    – v01pe
    Dec 19, 2014 at 16:03

You must log in to answer this question.

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