32

I have an awk script and I have passed a CSV file to it.

awk -f script.awk /home/abc/imp/asgd.csv

What am I doing is to get FILENAME within script.awk. FILENAME gives me the whole path. As I am in awk I cannot use basename FILENAME.

print FILENAME;
/home/abc/imp/asgd.csv

I have tried with this within script.awk

echo $FILENAME | awk -F"/" '{print $NF}'

but I cannot execute this within script.awk. How can I get asgd.csv within an awk program?

6 Answers 6

42

Several options:

awk '
  function basename(file) {
    sub(".*/", "", file)
    return file
  }
  {print FILENAME, basename(FILENAME)}' /path/to/file

Or:

awk '
  function basename(file, a, n) {
    n = split(file, a, "/")
    return a[n]
  }
  {print FILENAME, basename(FILENAME)}' /path/to/file

Note that those implementations of basename should work for the common cases, but not in corner cases like basename /path/to/x/// where they return the empty string instead of x or / where they return the empty string instead of /, though for regular files, that should not happen.

The first one will not work properly if the file paths (up to the last /) contain sequences of bytes that don't form valid characters in the current locale (typically this kind of thing happens in UTF-8 locales with filenames encoded in some 8 bit single byte character set). You can work around that by fixing the locale to C where every sequence of byte form valid characters.

1
  • 6
    If you need code that will work easily within an existing awk script without introducing a function, you should use: n = split(FILENAME, a, "/"); basename=a[n];. Don't use sub as that will actually change the FILENAME variable (which is a non-issue with the function since awk uses call by value).
    – shiri
    Commented Jan 7, 2018 at 9:28
11

Try this awk one-liner,

$ awk 'END{ var=FILENAME; split (var,a,/\//); print a[5]}' /home/abc/imp/asgd.csv
asgd.csv
2
  • 4
    or awk 'END{ var=FILENAME; n=split (var,a,/\//); print a[n]}' /home/abc/imp/asgd.csv Commented Jun 3, 2014 at 8:39
  • You should edit the solution to put the n= , a [n] version (proposing a solution with a "magic number" which depends not only on the file location, but aldo on the invoker's location (could use relative paths...) is not quite optimal and will only work when the provided path has 4 slashes before the relevant last part ) Commented Sep 4 at 14:05
8

On the systems where basename command is available, one could use awk's system() function or expression | getline var structure to call external basename command. This can help accounting for corner cases mentioned in Stephane's answer.

$ awk '{cmd=sprintf("basename %s",FILENAME);cmd | getline out; print FILENAME,out; exit}' /etc///passwd
/etc///passwd passwd
3
  • 1
    This should be top answer. It's much safer to get basename command than to split by slashes and hope that there is no \/ in the filename Commented Mar 16, 2020 at 11:59
  • It basically introduces an arbitrary command execution vulnerability as you're not properly escaping the filename in the shell code that you generate in cmd. For instance, if you run awk '...that...' ./*.txt in a directory that contains a file called $(reboot).txt, it will reboot. Commented Sep 4 at 13:01
  • Also getline only reads the first line (well record, that's dependent on the value of RS) of the output of the command, so you'll only be getting the first line of the file name (for those file names that are made of several lines). Commented Sep 4 at 13:02
2

Use Awk's Split Function

One way to do this is to use the split function. For example:

awk '{idx = split(FILENAME, parts, "/"); print parts[idx]; nextfile}' /path/to/file

This even works on multiple files. For example:

$ awk '{idx = split(FILENAME, parts, "/"); print parts[idx]; nextfile}' \
      /etc/passwd /etc/group
passwd
group
0

Use gensub function

If you want to use it directly and only once, you can rely on gensub:

awk -F= '$1=="User" {print gensub(/.*\//, "","g",FILENAME), $2;}' /opt/ops/resources/Int_Layer_Config.sh
Int_Layer_Config.sh svcOpsInt02

  • second argument is "replace by nothing"
  • third argument is for repeating the pattern. it could be 1 instead since .* is greedy, but "g" is a best practice on more restrictive patterns.

And you can still assign it if you prefer, readibility in mind:

awk -F= '$1=="User" {fname=gensub(/.*\//, "","g",FILENAME); print fname, $2;}' /opt/ops/resources/Int_Layer_Config.sh
Int_Layer_Config.sh svcOpsInt02

Below a more complex usage when the filename has a meaning in the data you process. I want the first part of the name, before _

awk -F= '$1=="User" {envname=gensub(/_.*$/,"","g",gensub(/.*\//, "","g",FILENAME)); print envname, $2;}' /opt/ops/resources/*_Layer_Config.sh
Int svcOpsInt02
Dev svcDev1
Qal svcDev3

NB: The same code works inside an awk script.

1
  • Note that gensub() is a non-standard GNU extension. Commented Sep 4 at 12:58
-1

the best way to export it from input CSV or directly from input file path you can reverse it , then get 1 column and then again reverse it.

function getFileFromPath() {
    FileName=$1
    cat $FileName | while read Filename
    do
        echo $Filename| rev | awk -v FS='/' '{print $1}' | rev 
    done
}

or simply

echo $FileNamePath| rev | awk -v FS='/' '{print $1}' | rev 

You must log in to answer this question.

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