0

I have a TSV file where each line represents a command and command line arguments. Like this:

ls      ~
cd      /home
cp      dir1    dir2

Each line may have a variable number of columns. Each cell may contain spaces, single quotes and double quotes.

What is a nice way to execute this?

William Entriken
  • 37,208
  • 23
  • 149
  • 195

2 Answers2

1

Using Bash, read into an array with the IFS set to a tab:

$ cat data
ls      ~
cd      /home
cp      dir1    dir2
al      a b       c  d  
$ vis -t -n data
ls^I~^J
cd^I/home^J
cp^Idir1^Idir2^J
al^Ia b^I  c  d  ^J
$ while IFS=$'\t' read -r -a xyz; do al "${xyz[@]}"; done < data
ls
~
cd
/home
cp
dir1
dir2
al
a b
  c  d  
$ while IFS=$'\t' read -r -a xyz; do al "${xyz[@]}"; done < data | vis -t -n
ls^J
~^J
cd^J
/home^J
cp^J
dir1^J
dir2^J
al^J
a b^J
  c  d  ^J
$

The vis program with options -t -n prints tabs as ^I and newlines as ^J (followed by a newline); the al program prints its arguments one per line — it is effectively equivalent to printf "%s\n" "$@" (it's actually a very simple C program, but the result is the same).

With real commands in the file to be executed, you'd write:

while IFS=$'\t' read -r -a xyz; do "${xyz[@]}"; done < data

On my machine, I got:

$ (while IFS=$'\t' read -r -a xyz; do "${xyz[@]}"; done < data )
ls: ~: No such file or directory
cp: dir1: No such file or directory
a b
  c  d  
$

I used a sub-shell because I didn't want to leave my current directory in my main shell, and I don't have a dir1 to be copied to dir2. Note that the shell did not do tilde-expansion on the results of the parameter expansion, so ls got to see an actual tilde, not the value of my home directory. Fixing the tilde expansion would be painful — excruciatingly painful (see Tilde expansion in quotes). It also means range notation such as {1..10} would not be expanded, and that aliases would not be expanded. See Shell expansions for what would and would not occur.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Actually this does not work if one of the arguments is blank. Also, does not work with zsh – William Entriken Apr 29 '15 at 15:08
  • Any sequence of one or more tabs is treated as a single argument separator. You didn't mention `zsh` in the question; the tags are [tag:bash] and [tag:xargs]. Do you know why it doesn't work in `zsh`? If you need empty fields respected, then you certainly have to modify this answer; you probably can't do it with shell and IFS. – Jonathan Leffler Apr 30 '15 at 01:56
0

Solution:

ruby -ne 'require "shellwords"; system $_.chomp.split("\t").shelljoin'

Proof:

Here is our testing script: cat printArguments.bash:

#!/bin/bash
args=("$@")
for ((i=0; i < $#; i++)) {
   echo "argument $((i+1)): ${args[$i]}"
}

And the test case:

echo $'./printArguments.bash\t1\t\t3' | ruby -ne 'require "shellwords"; system $_.chomp.split("\t").shelljoin'

The result:

argument 1: 1

argument 2:

argument 3: 3

Community
  • 1
  • 1
William Entriken
  • 37,208
  • 23
  • 149
  • 195