0

I have some questions about using the basename function in bash.

I have some paths stored in a file called filename2.txt, it looks something like this:

/data/sequedas/Miseq2/17031_M0446
/data/sequedas/Miseq2/17022_N0213
/data/sequedas/Miseq2/28103_N3123
...

So I was running a loop trying to do something to each of the folder above:

while read line; 
do interop_imaging_table $line > basename $line ; 
done < filename2.txt

Basically I want to use basename $line to remove everything in the path of the filename except the last part. So that I would be left with the following in the filename by using basename $line:

17031_M0446
17022_N0213
28103_N3123

But unfortunately it doesn't work for some reasons. Basically the output will all output to the file called basename. But I was hoping it would output separately to files called 17031_M0446, 17022_N0213, ....

Barmar
  • 741,623
  • 53
  • 500
  • 612
john_w
  • 693
  • 1
  • 6
  • 25
  • 1
    `interop_imaging_table $line > basename $line` Is the same as `interop_imaging_table $line $line > basename`, which writes output to a file named "basename". Perhaps your question is missing some backticks? – William Pursell Jul 16 '19 at 19:56
  • Can anybody comment on why basename is printing the last value only? I have seen that behavior, in ``for files in $(ls -v some/path/*.*); do basename $files; done`` – quanta Apr 17 '20 at 01:36

2 Answers2

1

Here are two ways to do that.

As a note, I've added some extra quotes to better support any spaces or other unexpected characters in your paths.

First, command substitution:

while read line
  do interop_imaging_table "$line" > "$(basename "$line")"
done < filename2.txt

(Don't worry, the "$(…)" structure allows one level of nested quotes even though SO's syntax highlighting doesn't understand it. I'm not sure if that works with the deprecated "`…`" syntax.)

Second, built-in substitution via parameter expansion:

while read line
  do interop_imaging_table "$line" > "${line##*/}"
done < filename2.txt

"${line##*/}" instructs the shell (bash in your case, but this is POSIX-compliant so it'll work in nearly any shell) to remove characters from the front of the string up to (and including) the final / character. (If you were to say "${line#*/}" then you'd remove characters from the front of the string up to (and including) the first / character.

Similarly, "${line%%/*}" would strip the text from the end up to (and including) the first / character (in this case, you'd end up with empty strings since they start with /). "${line%/*}" is basically the same as "$(dirname "$line")" in that it removes all characters starting with the final /.

To add a suffix to the file name, just add it before the final quote, like "$(basename "$line").txt" or "${line##*/}.txt"

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
  • `"${line##*/}"` doesn't spawn a subprocess. :) – Paul Hodges Jul 16 '19 at 20:35
  • 1
    Correct. That's why I personally prefer it, though it is less legible to those who aren't highly versed in string manipulation for bash/POSIX shell scripting. If the loop is really long, the myriads of calls to `basename` will add up to a notable performance hit. – Adam Katz Jul 16 '19 at 20:45
0

You have to use $() to substitute the output of a command back into the command line:

interop_imaging_table "$line" > "$(basename "$line")"

What you wrote is equivalent to writing

interop_imaging_table $line $line  > basename

because > just uses the next word as the filename.

You should also remember to quote variables unless you specifically need word splitting to be done on the result.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • thanks for your quick input, but may I know for example how to add a suffix to the file name? for example if I want the output to be called 17031_M0446.csv, 17022_N0213.csv ... , where should i put the csv literals in the code? Also could you kindly explain little bit more about what is the meaning of "You should also remember to quote variables unless you specifically need word splitting to be done on the result.", because I am very new to bash or programming in general. – john_w Jul 16 '19 at 20:10
  • Just add the suffix after the command substitution: `> "$(basename "$line").csv"` – Barmar Jul 16 '19 at 20:16
  • or `> "${line##*/}.csv"` – Paul Hodges Jul 16 '19 at 20:37
  • Yeah, I didn't feel like showing alternatives to `basename`, this is more instructive of a general process of substituting command output. – Barmar Jul 16 '19 at 20:39
  • 1
    thanks, but could you explain what you mean when you said "You should also remember to quote variables unless you specifically need word splitting to be done on the result"? Thanks a lot. – john_w Jul 16 '19 at 22:34
  • 1
    @john_w See https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else – Barmar Jul 17 '19 at 14:17