variables in find command and more shell problem

8,023

Solution 1

You need to take care of several things

  • double quotes to protect the content of a variable from shell interpretation. IFS contain probably by default "space+tab+newline", so : NEWIFS=IFS is probably going to end up putting nothing in NEWIFS.

    SAVEIFS="$IFS"
    ...
    IFS="$(echo -en "\n\b")"   
    #yes, you can have " inside $( ... ) : the shell will gladly interpret them by
    # order of depth inside the $() construct. 
    # A big advantage over using backquotes where it would get mixed up and almost unreadable
    ...
    IFS="$SAVEIFS"
    
  • Don't use reserved words as variable names (avoid for let etc. Avoid also many "common" variable names, ie LINES, COLUMNS, etc.

    • why aliasing export to export? looks like a good way to create a loop if the shell is old, or at best it's useless.

    • precising -name * means you avoid any filename or dirnames starting with a ..

    • -exec sh .... will launch it in an sh subshell: any variables you declare in there will be lost once that sh exits out to the invoking shell.

You need something like:

#!/bin/bash
for adirectory in "$@" ; do
    find "$adirectory" -type f -name '*.3gp' -print | while IFS= read -r wholeline ; do
       mylength=$(echo "$wholeline" | wc -c)  #Not needed, just kept as exemple of a way to achieve it
       myfilewithoutext="$(echo "$wholeline" | sed -e 's/.3gp$//' )"  
          #keeps only the filename without its ".3gp" or ".3GP" extension.
       ffmpeg -y -i "$wholeline"  "${myfilewithoutext}.mp3" > /dev/null   #?  please check if it works
    done #end of the find|while
done #end of the for looping on all the arguments passed to the script (dir names)

IFS in that while IFS= read -r wholeline ; do will be set to the empty string ONLY during the invocation of the read -r wholeline. Untouched outside of it. VAR="something" command parameters will invoke command parameters, with the value of VAR temporarily set to something. Here we set IFS to '', i.e. nothing. It will still be passed line by line, and as IFS is empty the whole line ends up read by read wholeline.

Solution 2

As per comment (converting *.3gp to *.mp3):

for n in *.3gp; do
    ffmpeg -y -i "$n"  "${n%.3gp}.mp3"
done

As per olivier's comment, to also act on files in subdirectories, *.3gp can be replaced with $(find ...), provided the filenames don't need escaping (namely they don't contain whitespace or globbing characters \[?*). Alternatively **/*.3gp can be used (in bash, put shopt -s extglob in your .bashrc).

Share:
8,023

Related videos on Youtube

PersianGulf
Author by

PersianGulf

My God is Allah and my religious book is Quran.Up to now my life was based on my God's will and whatever I gained or lost is due to my God's want. My aspiration is to put my life in favour of my lord, Allah since I believe this would be a desirable life. I am a fan and supporter of Free Software and i do all my computer works based on Freesoftware. My other interests are Linux/GNU family and BSD. My main speciality is network administration but I also have decent knowledge in C/C++ and Python progmming. In general I love programming languages. My favorite sport is mountain climbing because I love outdoors and generaly I'm a nature lover. I truely love to increase my knowledge because I'm eager to know as much as possible. Not only I have this desire for myself, but also for every human being and I will do my best to help anyone who wishes to be on this path. (You can find everythinf about me at http://pahlevanzadeh.net)

Updated on September 18, 2022

Comments

  • PersianGulf
    PersianGulf over 1 year

    Possible Duplicate:
    Recursive rename files and directories

    I wrote the following script:

    #!/bin/bash
    SAVEIFS=$IFS
    alias export='export'
    IFS=$(echo -en "\n\b")
    
              find $1 -name "*" -a -type f -exec sh -c let  len=`expr length {}` \;  -exec  sh -c let  str=`expr substr {} 1 len-3` \; -exec ffmpeg -y -i {}  $str.mp3 \;
    
    # restore $IFS
    unalias export
    IFS=$SAVEIFS
    

    Problem:

    You know when more one shell you can't export your variable in other shell, So i need to :

    1. use variables

    2. don't use run shell

    So, how i do it?

    When run the following script:

    find $1 -name "*" -a -type f -exec let  len=`expr length {}` \;  -exec let  str=`expr substr {} 1 len-3` \; -exec ffmpeg -y -i {}  $str.mp3 \; 
    # Note : above script doesn't has 'sh -c' 
    

    I get the following error:

    find: `let': No such file or directory
    

    I tested it with export or delete export or let , i discovered -exec has problem with built-in shell command....!

    Do you have any idea????

    • Admin
      Admin over 11 years
      Can you sum up what you want to accomplish with your script? What's the input and the desired output?
    • PersianGulf
      PersianGulf over 11 years
      My input set of *.3gp files, and i convert them to *.mp3 files, in first step of script i wrote successfully, but when i convert, suppose i do have myfile.3gp and when i convert it renamed to myfile.3gp.mp3 , i used expr for solve it.
  • Olivier Dulac
    Olivier Dulac over 11 years
    neat approach. easy to read, and nice handling of the spaces in filenames. A caveat: won't find the 3gp file in the subdirs. Actually it find them in "the current dir, and only the first level" instead of "all files underneath directory $1" as @Mohsen Pahlevanzadeh apparently intended...
  • peterph
    peterph over 11 years
    @OlivierDulac good point :)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    while IFS= read -r line has come up several times, I think Why is while IFS= read used so often, instead of IFS=; while read..? is our definitive post on the subject. I haven't reviewed your script fully. These are good explanations. The script is still more complex and brittle than it needs to be, in particular the call to sed should be replaced by built-in shell constructs: myfilewithoutext=${wholeline#.3gp}.
  • Olivier Dulac
    Olivier Dulac over 11 years
    @Gilles: I use bash versions that are so old they can't handle those "fancy" constructs ^^ . I try to keep it portable (albeit, I agree, at the expense of sometimes having to do some ugly hacks). Thanks for the reformatting, I'll edit again to weed out old stuff (like my questions about the extension, which were answered by other comments)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    AFAIR bash has always supported ${…#…} and friends. It's in POSIX. Most Bourne shells don't have it, but systems without a POSIX shells are getting far and few.
  • Olivier Dulac
    Olivier Dulac over 11 years
    Thanks for that heads-up, I'll have a look to see if our system's bash supports it. But it's old (version 2.0.something) (no "$LINENO", no "$BASH_SOURCE", etc...)
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 11 years
    ${VAR#PREFIX} was already supported in bash 1.14. I can't easily find information about earlier versions. In BSD, it seems to have appeared only in 4.4BSD, so some time during the FreeBSD-2.x/NetBSD-1.x series (according to the manpages).
  • nino
    nino over 8 years
    for a script to fire the ffmpeg, it's recommend to add < /dev/null to the end of the ffmpeg command line. You need the script to "push" the execution to ffmpeg without hanging.