2

I have the following bash script:

#!/bin/bash

find . -maxdepth 1 -mmin +1 -type f -name "240*.ts" 
| xargs -L 1 bash -c 'mv "${1}" "$(get_crtime${1} | awk '{print $5}').ts"' \;

The idea is to find files that are older than one minute matching a certain pattern (in my case, files that start with '240') and rename them from their original name (240-1458910816045.ts) to a desired format (15:00:16.ts).

Inside the script I am using get_crtime command which is a custom function included in /etc/bash.bashrc and has the following implementation:

get_crtime() {

    for target in "${@}"; do
        inode=$(stat -c '%i' "${target}")
        fs=$(df "${@}" | awk '{a=$1}END{print a}')
        crtime=$(sudo debugfs -R 'stat <'"${inode}"'>' "${fs}" 2>/dev/null |
        grep -oP 'crtime.*--\s*\K.*')
        printf "%s\t%s\n" "${target}" "${crtime}"
    done
}

When I call the function from the shell, like this:

get_crtime 240-1458910816045.ts | awk '{print $5}'

I get the desired output:

15:00:16

Which is a portion from the file creation date.

My problem is when I include the function call inside my initial script I get the following error:

}).ts": -c: line 0: unexpected EOF while looking for matching `)'
}).ts": -c: line 1: syntax error: unexpected end of file

I think this is caused by incorrect invoking of awk, so I thought to remove it and leave just:

find . -maxdepth 1 -mmin +1 -type f -name "240*.ts" 
| xargs -L 1 bash -c 'mv "${1}" "$(get_crtime ${1}).ts"' \;

I get the following error, which is more suggestive:

;: get_crtime: command not found

How can I call the custom function inside the bashrc inside the initial command without getting the last error?

Thank you!

  • The OS is Ubuntu
  • The shell is bash
5
  • 1
    You can't. xargs is starting a separate bash process that doesn't know anything about your functions or local variables. I suggest you to stop using xargs and friends, and learn about shell scripting Commented Mar 25, 2016 at 13:22
  • You could do it as a for loop instead. Since you want to keep the find do it like for file in $(find ...); do mv "$file" "$(get_crtime "$file")"; done Commented Mar 25, 2016 at 13:44
  • @KurtStutsman never write a for loop that runs on the output of a command like that as it's prone to breakage given some file names. Use cmd | while IFS= read -r file or similar instead. Commented Mar 25, 2016 at 13:49
  • @EdMorton there is nothing wrong with writing it that way if you know the file names will be well formed as in his case. There are only a minor number of cases where that is not true. Also using a while loop forces your code into a subshell and introduces problems of its own. Commented Mar 25, 2016 at 13:54
  • 2
    That's like saying there is nothing wrong with leaving shell variables unquoted if you know their contents will be well formed. Just do it the robust way and you won't get any surprises and/or form bad habits. If executing a subshell as a consequence creates a problem, use process substitution or one of the other workarounds, see mywiki.wooledge.org/BashFAQ/024, or reconsider your approach. Commented Mar 25, 2016 at 14:08

3 Answers 3

1

You can't use single quotes inside a single-quote delimited script. Look:

$ bash -c 'printf "%s\n" "$(date | awk '{print $0}')"'
-bash})": -c: line 0: unexpected EOF while looking for matching `)'
-bash})": -c: line 1: syntax error: unexpected end of file

$ bash -c 'printf "%s\n" "$(date | awk "{print \$0}")"'
Fri, Mar 25, 2016  8:59:31 AM

I'm not recommending you use double quotes around your awk script though - create a script to do the mv, etc. for you or figure out some other way to implement it that'll solve your function access problem too.

Sign up to request clarification or add additional context in comments.

Comments

0

In this example, used modification time of file, which can be get by stat -c '%y'. The xargs -I param creates possibility to place file name two times, first for stat, second for mv. Then using Parameter Expansion bash features to extract only time from human readable stat output:

find . -maxdepth 1 -mmin +1 -type f -name "240*.ts" | \
xargs -I_ bash -c 'MTIME=$(stat -c '%y' "_") && MTIME=${MTIME#* } && mv "_" ${MTIME%.*}.ts'

Comments

0

You need to export the function:

export -f get_crtime

That will make it available to child bash processes (but not to other shells).

Also, as @EdMorton points out, you cannot use single quotes inside a single quoted-string, which was the problem with the invocation of awk. So you'll need to come up with a different way of quoting the interior argument to awk, or fix get_crtime to just return the string you want.

By the way, you might consider using finds -exec action instead of xargs. That would allow you to use a loop over a number of files, which would be a bit more efficient.

eg.

find . -maxdepth 1 -mmin +1 -type f -name "240*.ts" \
     -exec bash -c 'for f in "$@"; do
                      mv "$f" "$(get_crtime "$f" | awk {print\$5}).ts"
                    done' _ {} +

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.