Bee Jay
Bee Jay

Reputation: 203

how to pass asterisk into ls command inside bash script

Hi… Need a little help here…

I tried to emulate the DOS' dir command in Linux using bash script. Basically it's just a wrapped ls command with some parameters plus summary info. Here's the script:

#!/bin/bash

# default to current folder 
if [ -z "$1" ]; then var=.;
  else var="$1"; fi

# check file existence
if [ -a "$var" ]; then
  # list contents with color, folder first
  CMD="ls -lgG $var --color --group-directories-first"; $CMD;

  # sum all files size
  size=$(ls -lgGp "$var" | grep -v / | awk '{ sum += $3 }; END { print sum }')
  if [ "$size" == "" ]; then size="0"; fi 

  # create summary
  if [ -d "$var" ]; then 
    folder=$(find $var/* -maxdepth 0 -type d | wc -l)
    file=$(find $var/* -maxdepth 0 -type f | wc -l)
    echo "Found: $folder folders "
    echo "       $file files $size bytes" 
  fi 
# error message 
else 
  echo "dir: Error \"$var\": No such file or directory"
fi

The problem is when the argument contains an asterisk (*), the ls within the script acts differently compare to the direct ls command given at the prompt. Instead of return the whole files list, the script only returns the first file. See the video below to see the comparation in action. I don't know why it behaves like that.

Anyone knows how to fix it? Thank you.

Video: problem in action


UPDATE:

The problem has been solved. Thank you all for the answers. Now my script works as expected. See the video here: http://i.giphy.com/3o8dp1YLz4fIyCbOAU.gif

Upvotes: 3

Views: 9277

Answers (5)

Rea Haas
Rea Haas

Reputation: 2528

The best solution I have is to use the eval command, in this way:

#!/bin/bash

cmd="some command \"with_quetes_and_asterisk_in_it*\""
echo "$cmd"
eval $cmd

The eval command takes its arguments and evaluates them into the command as the shell does. This solves my problem when I need to call a command with asterisk '*' in it from a script.

Upvotes: 0

RedX
RedX

Reputation: 15175

Everbody is giving you valuable advice which you should definitely should follow!

But here is the real answer to your question.

To pass unexpanded arguments to any executable you need to single quote them:

./your_script '*'

Upvotes: 1

clt60
clt60

Reputation: 63922

Both above answers already answered your question. So, i'm going a bit more verbose.

In your terminal is running the bash interpreter (probably). This is the program which parses your input line(s) and doing "things" based on your input.

When you enter some line the bash start doing the following workflow:

  1. parsing and lexical analysis
  2. expansion
    1. brace expansion
    2. tidle expansion
    3. variable expansion
    4. artithmetic and other substitutions
    5. command substitution
    6. word splitting
    7. filename generation (globbing)
  3. removing quotes

Only after all above the bash

  • will execute some external commands, like ls or dir.sh... etc.,
  • or will do so some "internal" actions for the known keywords and builtins like echo, for, if etc...

As you can see, the second last is the filename generation (globbing). So, in your case - if the test* matches some files, your bash expands the willcard characters (aka does the globbing).

So,

  • when you enter dir.sh test*,
  • and the test* matches some files
  • the bash does the expansion first
  • and after will execute the command dir.sh with already expanded filenames
  • e.g. the script get executed (in your case) as: dir.sh test.pas test.swift

BTW, it acts exactly with the same way for your ls example:

  • the bash expands the ls test* to ls test.pas test.swift
  • then executes the ls with the above two arguments
  • and the ls will print the result for the got two arguments.
  • with other words, the ls don't even see the test* argument - if it is possible - the bash expands the wilcard characters. (* and ?).

Now back to your script: add after the shebang the following line:

echo "the $0 got this arguments: $@"

and you will immediatelly see, the real argumemts how your script got executed.

also, in such cases is a good practice trying to execute the script in debug-mode, e.g.

bash -x dir.sh test*

and you will see, what the script does exactly.

Also, you can do the same for your current interpreter, e.g. just enter into the terminal

set -x

and try run the dir.sh test* = and you will see, how the bash will execute the dir.sh command. (to stop the debug mode, just enter set +x)

Upvotes: 3

Cong Ma
Cong Ma

Reputation: 11302

This is because when you retrieve $1 you assume the shell does NOT expand *.

In fact, when * (or other glob) matches, it is expanded, and broken into segments by $IFS, and then passed as $1, $2, etc.

You're lucky if you simply retrieved the first file. When your first file's path contains spaces, you'll get an error because you only get the first segment before the space.

Seriously, read this and especially this. Really.


And please don't do things like

CMD=whatever you get from user input; $CMD;

You are begging for trouble. Don't execute arbitrary string from the user.

Upvotes: 5

choroba
choroba

Reputation: 241908

The asterisk * is expanded by the shell when it parses the command line. In other words, your script doesn't get a parameter containing an asterisk, it gets a list of files as arguments. Your script only works with $1, the first argument. It should work with "$@" instead.

Upvotes: 7

Related Questions