Parsing 在shell脚本中解析URL

Parsing 在shell脚本中解析URL,parsing,shell,url,Parsing,Shell,Url,我有如下网址: sftp://user@host.net/some/random/path 我想从这个字符串中提取用户、主机和路径。任何零件都可以是随机长度。[EDIT 2019] 这个答案并不意味着是一个包罗万象、无所不能的解决方案。它本打算为基于python的版本提供一个简单的替代方案,结果它比原来的版本具有更多的功能 它只以bash的方式回答了基本问题,然后我自己多次修改,加入了一手评论的要求。然而,我认为在这一点上,增加更多的复杂性将使其无法维护。我知道并非所有事情都是直截了当的(例

我有如下网址:

sftp://user@host.net/some/random/path

我想从这个字符串中提取用户、主机和路径。任何零件都可以是随机长度。

[EDIT 2019] 这个答案并不意味着是一个包罗万象、无所不能的解决方案。它本打算为基于python的版本提供一个简单的替代方案,结果它比原来的版本具有更多的功能


它只以bash的方式回答了基本问题,然后我自己多次修改,加入了一手评论的要求。然而,我认为在这一点上,增加更多的复杂性将使其无法维护。我知道并非所有事情都是直截了当的(例如,检查有效端口需要比较
hostport
host
),但我不想增加更多的复杂性


[原始答案]

假设URL作为第一个参数传递给脚本:

#!/bin/bash

# extract the protocol
proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
# remove the protocol
url="$(echo ${1/$proto/})"
# extract the user (if any)
user="$(echo $url | grep @ | cut -d@ -f1)"
# extract the host and port
hostport="$(echo ${url/$user@/} | cut -d/ -f1)"
# by request host without port    
host="$(echo $hostport | sed -e 's,:.*,,g')"
# by request - try to extract the port
port="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')"
# extract the path (if any)
path="$(echo $url | grep / | cut -d/ -f2-)"

echo "url: $url"
echo "  proto: $proto"
echo "  user: $user"
echo "  host: $host"
echo "  port: $port"
echo "  path: $path"
我必须承认这不是最干净的解决方案,但它不依赖于其他脚本 像perl或python这样的语言。 (提供使用其中一种的解决方案将产生更清洁的结果;)

使用您的示例,结果如下:

url: user@host.net/some/random/path
  proto: sftp://
  user: user
  host: host.net
  port:
  path: some/random/path
这也适用于没有协议/用户名或路径的URL。 在这种情况下,相应的变量将包含一个空字符串

[编辑]
如果bash版本无法处理替换(${1/$proto/}),请尝试以下方法:

#!/bin/bash

# extract the protocol
proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"

# remove the protocol -- updated
url=$(echo $1 | sed -e s,$proto,,g)

# extract the user (if any)
user="$(echo $url | grep @ | cut -d@ -f1)"

# extract the host and port -- updated
hostport=$(echo $url | sed -e s,$user@,,g | cut -d/ -f1)

# by request host without port
host="$(echo $hostport | sed -e 's,:.*,,g')"
# by request - try to extract the port
port="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')"

# extract the path (if any)
path="$(echo $url | grep / | cut -d/ -f2-)"
使用Python(这项工作的最佳工具,IMHO):

进一步阅读:

以上内容经过改进(添加了密码和端口解析),并在/bin/sh中工作:

# extract the protocol
proto="`echo $DATABASE_URL | grep '://' | sed -e's,^\(.*://\).*,\1,g'`"
# remove the protocol
url=`echo $DATABASE_URL | sed -e s,$proto,,g`

# extract the user and password (if any)
userpass="`echo $url | grep @ | cut -d@ -f1`"
pass=`echo $userpass | grep : | cut -d: -f2`
if [ -n "$pass" ]; then
    user=`echo $userpass | grep : | cut -d: -f1`
else
    user=$userpass
fi

# extract the host -- updated
hostport=`echo $url | sed -e s,$userpass@,,g | cut -d/ -f1`
port=`echo $hostport | grep : | cut -d: -f2`
if [ -n "$port" ]; then
    host=`echo $hostport | grep : | cut -d: -f1`
else
    host=$hostport
fi

# extract the path (if any)
path="`echo $url | grep / | cut -d/ -f2-`"

我需要它,所以我写了它(显然是基于@Shirkin的答案),我想其他人可能会喜欢它。

我不喜欢上面的方法,而是自己写的。它用于ftp链接,如果需要,只需将
ftp
替换为
http
。 第一行是链接的一个小验证,链接应该看起来像
ftp://user:pass@host.com/path/to/something

if ! echo "$url" | grep -q '^[[:blank:]]*ftp://[[:alnum:]]\+:[[:alnum:]]\+@[[:alnum:]\.]\+/.*[[:blank:]]*$'; then return 1; fi

login=$(  echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\1|' )
pass=$(   echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\2|' )
host=$(   echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\3|' )
dir=$(    echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\4|' )
我的实际目标是通过url检查ftp访问。以下是全部结果:

#!/bin/bash

test_ftp_url()  # lftp may hang on some ftp problems, like no connection
    {
    local url="$1"

    if ! echo "$url" | grep -q '^[[:blank:]]*ftp://[[:alnum:]]\+:[[:alnum:]]\+@[[:alnum:]\.]\+/.*[[:blank:]]*$'; then return 1; fi

    local login=$(  echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\1|' )
    local pass=$(   echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\2|' )
    local host=$(   echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\3|' )
    local dir=$(    echo "$url" | sed 's|[[:blank:]]*ftp://\([^:]\+\):\([^@]\+\)@\([^/]\+\)\(/.*\)[[:blank:]]*|\4|' )

    exec 3>&2 2>/dev/null
    exec 6<>"/dev/tcp/$host/21" || { exec 2>&3 3>&-; echo 'Bash network support is disabled. Skipping ftp check.'; return 0; }

    read <&6
    if ! echo "${REPLY//$'\r'}" | grep -q '^220'; then exec 2>&3  3>&- 6>&-; return 3; fi   # 220 vsFTPd 3.0.2+ (ext.1) ready...

    echo -e "USER $login\r" >&6; read <&6
    if ! echo "${REPLY//$'\r'}" | grep -q '^331'; then exec 2>&3  3>&- 6>&-; return 4; fi   # 331 Please specify the password.

    echo -e "PASS $pass\r" >&6; read <&6
    if ! echo "${REPLY//$'\r'}" | grep -q '^230'; then exec 2>&3  3>&- 6>&-; return 5; fi   # 230 Login successful.

    echo -e "CWD $dir\r" >&6; read <&6
    if ! echo "${REPLY//$'\r'}" | grep -q '^250'; then exec 2>&3  3>&- 6>&-; return 6; fi   # 250 Directory successfully changed.

    echo -e "QUIT\r" >&6

    exec 2>&3  3>&- 6>&-
    return 0
    }

test_ftp_url 'ftp://fz223free:fz223free@ftp.zakupki.gov.ru/out/nsi/nsiProtocol/daily'
echo "$?"
#/bin/bash
test_ftp_url()#lftp可能会挂起一些ftp问题,例如没有连接
{
本地url=“$1”
如果
本地登录=$(回显“$url”| sed的|[:blank:][]*ftp://\([^:]\+\):\([^@]\+\)@([^/]\+\)\(/.\)[:blank:]*.\1 |')
本地传递=$(回显“$url”| sed的|[:blank:][]*ftp://\([^:][\+\):\([^@]\+\)@([^/]\+\)\(/.\)[:blank:][]*.\2 |')
本地主机=$(回显“$url”| sed的|[:blank:][]*ftp://\([^:][\+\):\([^@]\+\)@([^/]\+\)\(/.\[:blank:][]*\\3 |')
本地目录=$(回显“$url”| sed的|[:blank:][]*ftp://\([^:][\+\):\([^@]\+\)@([^/]\+\)\(/.\)[:blank:][]*\\4 |')
exec 3>&2>/dev/null
exec 6“/dev/tcp/$host/21”|{exec 2>&3>和-;echo“Bash网络支持已禁用。正在跳过ftp检查。”;返回0;}
读取&3>&-6>&-;返回3;fi#220 vsFTPd 3.0.2+(ext.1)就绪。。。
echo-e“USER$login\r”>&6;read&3>&-6>&-;return 4;fi#331请指定密码。
echo-e“PASS$PASS\r”>&6;read&3>&-6>&-;return 5;fi#230登录成功。
echo-e“CWD$dir\r”>&6;read&3>&-6>&-;return 6;fi#250目录已成功更改。
echo-e“退出\r”>&6
执行2>&3>&6>&-
返回0
}
测试\u ftp\u url'ftp://fz223free:fz223free@ftp.zakupki.gov.ru/out/nsi/nsiProtocol/daily'
回声“$?”

如果您真的想在shell中完成,可以使用awk完成以下简单的操作。这需要知道实际传递的字段数(例如,有时没有密码,有时没有密码)

如果您没有awk,但有grep,并且您可以要求每个字段至少包含两个字符,并且在格式上可以合理预测,那么您可以执行以下操作:

#!/bin/bash

FIELDS=($(echo "sftp://user@host.net/some/random/path" \
   | grep -o "[a-z0-9.-][a-z0-9.-]*" | tr '\n' ' '))
proto=${FIELDS[1]}
user=${FIELDS[2]}
host=${FIELDS[3]}
path=$(echo ${FIELDS[@]:3} | sed 's/ /\//g')

只是需要做同样的事情,所以我很好奇是否可以在一行中完成,这就是我得到的:

#!/bin/bash

parse_url() {
  eval $(echo "$1" | sed -e "s#^\(\(.*\)://\)\?\(\([^:@]*\)\(:\(.*\)\)\?@\)\?\([^/?]*\)\(/\(.*\)\)\?#${PREFIX:-URL_}SCHEME='\2' ${PREFIX:-URL_}USER='\4' ${PREFIX:-URL_}PASSWORD='\6' ${PREFIX:-URL_}HOST='\7' ${PREFIX:-URL_}PATH='\9'#")
}

URL=${1:-"http://user:pass@example.com/path/somewhere"}
PREFIX="URL_" parse_url "$URL"
echo "$URL_SCHEME://$URL_USER:$URL_PASSWORD@$URL_HOST/$URL_PATH"
工作原理:

  • 有一个疯狂的sed正则表达式可以捕获url的所有部分,而所有部分都是可选的(除了主机名)
  • 使用这些捕获组sed输出环境变量名称及其相关部分的值(如URL\u方案或URL\u用户)
  • eval执行该输出,使这些变量导出并在脚本中可用
  • 可以选择将前缀传递给控制输出环境变量名称

  • PS:在将此用于任意输入时要小心,因为此代码容易受到脚本注入的攻击。

    以下是我的看法,大致基于一些现有答案,但它也可以处理GitHub SSH克隆URL:

    #!/bin/bash
    
    PROJECT_URL="git@github.com:heremaps/here-aaa-java-sdk.git"
    
    # Extract the protocol (includes trailing "://").
    PARSED_PROTO="$(echo $PROJECT_URL | sed -nr 's,^(.*://).*,\1,p')"
    
    # Remove the protocol from the URL.
    PARSED_URL="$(echo ${PROJECT_URL/$PARSED_PROTO/})"
    
    # Extract the user (includes trailing "@").
    PARSED_USER="$(echo $PARSED_URL | sed -nr 's,^(.*@).*,\1,p')"
    
    # Remove the user from the URL.
    PARSED_URL="$(echo ${PARSED_URL/$PARSED_USER/})"
    
    # Extract the port (includes leading ":").
    PARSED_PORT="$(echo $PARSED_URL | sed -nr 's,.*(:[0-9]+).*,\1,p')"
    
    # Remove the port from the URL.
    PARSED_URL="$(echo ${PARSED_URL/$PARSED_PORT/})"
    
    # Extract the path (includes leading "/" or ":").
    PARSED_PATH="$(echo $PARSED_URL | sed -nr 's,[^/:]*([/:].*),\1,p')"
    
    # Remove the path from the URL.
    PARSED_HOST="$(echo ${PARSED_URL/$PARSED_PATH/})"
    
    echo "proto: $PARSED_PROTO"
    echo "user: $PARSED_USER"
    echo "host: $PARSED_HOST"
    echo "port: $PARSED_PORT"
    echo "path: $PARSED_PATH"
    

    proto:
    user: git@
    host: github.com
    port:
    path: :heremaps/here-aaa-java-sdk.git
    
    对于
    PROJECT\u URL=”ssh://sschuberth@git.eclipse.org:29418/jgit/jgit“

    proto: ssh://
    user: sschuberth@
    host: git.eclipse.org
    port: :29418
    path: /jgit/jgit
    

    我做了进一步的解析,扩展了@Shirkrin给出的解决方案:

    #!/bin/bash
    
    parse_url() {
        local query1 query2 path1 path2
    
        # extract the protocol
        proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
    
        if [[ ! -z $proto ]] ; then
                # remove the protocol
                url="$(echo ${1/$proto/})"
    
                # extract the user (if any)
                login="$(echo $url | grep @ | cut -d@ -f1)"
    
                # extract the host
                host="$(echo ${url/$login@/} | cut -d/ -f1)"
    
                # by request - try to extract the port
                port="$(echo $host | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')"
    
                # extract the uri (if any)
                resource="/$(echo $url | grep / | cut -d/ -f2-)"
        else
                url=""
                login=""
                host=""
                port=""
                resource=$1
        fi
    
        # extract the path (if any)
        path1="$(echo $resource | grep ? | cut -d? -f1 )"
        path2="$(echo $resource | grep \# | cut -d# -f1 )"
        path=$path1
        if [[ -z $path ]] ; then path=$path2 ; fi
        if [[ -z $path ]] ; then path=$resource ; fi
    
        # extract the query (if any)
        query1="$(echo $resource | grep ? | cut -d? -f2-)"
        query2="$(echo $query1 | grep \# | cut -d\# -f1 )"
        query=$query2
        if [[ -z $query ]] ; then query=$query1 ; fi
    
        # extract the fragment (if any)
        fragment="$(echo $resource | grep \# | cut -d\# -f2 )"
    
        echo "url: $url"
        echo "   proto: $proto"
        echo "   login: $login"
        echo "    host: $host"
        echo "    port: $port"
        echo "resource: $resource"
        echo "    path: $path"
        echo "   query: $query"
        echo "fragment: $fragment"
        echo ""
    }
    
    parse_url "http://login:password@example.com:8080/one/more/dir/file.exe?a=sth&b=sth#anchor_fragment"
    parse_url "https://example.com/one/more/dir/file.exe#anchor_fragment"
    parse_url "http://login:password@example.com:8080/one/more/dir/file.exe#anchor_fragment"
    parse_url "ftp://user@example.com:8080/one/more/dir/file.exe?a=sth&b=sth"
    parse_url "/one/more/dir/file.exe"
    parse_url "file.exe"
    parse_url "file.exe#anchor"
    

    如果您可以访问Bash>=3.0,那么也可以在纯Bash中执行此操作,这要感谢重新匹配操作符
    =~

    pattern='^([:alnum:]+)://)?([:alnum:]+)@)([^:^@]+)(:([:digit:]+)?$”
    如果[[”http://us@cos.com:3142“=~$pattern]];然后
    proto=${BASH_重赛[2]}
    用户=${BASH_重新匹配[4]}
    主机=${BASH_重新匹配[5]}
    端口=${BASH_重新匹配[7]}
    fi
    

    与前面的所有示例相比,它应该更快、更少地消耗资源,因为没有生成外部进程。

    此解决方案原则上与此线程中的工作原理相同,但改进了正则表达式(进行了一些更改),并修复了一些错误(例如,userinfo可以包含“uu”字符)。这还可以理解相对URI(例如,提取查询或片段)


    如果您有权访问Node.js:

    export MY_URI=sftp://user@host.net/some/random/path
    node -e "console.log(url.parse(process.env.MY_URI).user)"
    node -e "console.log(url.parse(process.env.MY_URI).host)"
    node -e "console.log(url.parse(process.env.MY_URI).path)"
    
    这将输出:

    user
    host.net
    /some/random/path
    

    仅从完整URL获取域的简单方法:

    echohttps://stackoverflow.com/questions/6174220/parse-url-in-shell-script |切口-d/-f1-3
    #输出>>>htt
    
    # !/bin/bash
    
    # Following regex is based on https://tools.ietf.org/html/rfc3986#appendix-B with
    # additional sub-expressions to split authority into userinfo, host and port
    #
    readonly URI_REGEX='^(([^:/?#]+):)?(//((([^:/?#]+)@)?([^:/?#]+)(:([0-9]+))?))?(/([^?#]*))(\?([^#]*))?(#(.*))?'
    #                    ↑↑            ↑  ↑↑↑            ↑         ↑ ↑            ↑ ↑        ↑  ↑        ↑ ↑
    #                    |2 scheme     |  ||6 userinfo   7 host    | 9 port       | 11 rpath |  13 query | 15 fragment
    #                    1 scheme:     |  |5 userinfo@             8 :…           10 path    12 ?…       14 #…
    #                                  |  4 authority
    #                                  3 //…
    
    parse_scheme () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[2]}"
    }
    
    parse_authority () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[4]}"
    }
    
    parse_user () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[6]}"
    }
    
    parse_host () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[7]}"
    }
    
    parse_port () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[9]}"
    }
    
    parse_path () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[10]}"
    }
    
    parse_rpath () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[11]}"
    }
    
    parse_query () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[13]}"
    }
    
    parse_fragment () {
        [[ "$@" =~ $URI_REGEX ]] && echo "${BASH_REMATCH[15]}"
    }
    
    export MY_URI=sftp://user@host.net/some/random/path
    node -e "console.log(url.parse(process.env.MY_URI).user)"
    node -e "console.log(url.parse(process.env.MY_URI).host)"
    node -e "console.log(url.parse(process.env.MY_URI).path)"
    
    user
    host.net
    /some/random/path
    
    echo https://stackoverflow.com/questions/6174220/parse-url-in-shell-script | cut -d/ -f4-
    
    # OUTPUT>>> questions/6174220/parse-url-in-shell-script
    
    echo https://stackoverflow.com/questions/6174220/parse-url-in-shell-script/59971653 | awk -F"/" '{ for (i=4; i<=NF; i++) printf"/%s", $i }'
    
    # OUTPUT>>> /questions/6174220/parse-url-in-shell-script/59971653
    
    #!/bin/bash
    
    #You can also use environment variable $NAUTILUS_SCRIPT_CURRENT_URI
    X="sftp://user@host.net/some/random/path"
    
    tmp=${X#*//};usr=${tmp%@*}
    tmp=${X#*@};host=${tmp%%/*};[[ ${X#*://} == *":"* ]] && host=${host%:*}
    tmp=${X#*//};path=${tmp#*/}
    proto=${X%:*}
    [[ ${X#*://} == *":"* ]] && tmp=${X##*:} && port=${tmp%%/*}
    
    echo "Potocol:"$proto" User:"$usr" Host:"$host" Port:"$port" Path:"$path
    
    http://example.com
    http://example.com/
    
    TEST_URLS=(
      https://github.com/briceburg/tools.git
      https://foo:12333@github.com:8080/briceburg/tools.git
      git@github.com:briceburg/tools.git
      https://me@gmail.com:12345@my.site.com:443/p/a/t/h
    )
    
    for url in "${TEST_URLS[@]}"; do
      without_proto="${url#*:\/\/}"
      without_auth="${without_proto##*@}"
      [[ $without_auth =~ ^([^:\/]+)(:[[:digit:]]+\/|:|\/)?(.*) ]]
      PROJECT_HOST="${BASH_REMATCH[1]}"
      PROJECT_PATH="${BASH_REMATCH[3]}"
    
      echo "given: $url"
      echo "  -> host: $PROJECT_HOST path: $PROJECT_PATH"
    done
    
    given: https://github.com/briceburg/tools.git
      -> host: github.com path: briceburg/tools.git
    given: https://foo:12333@github.com:8080/briceburg/tools.git
      -> host: github.com path: briceburg/tools.git
    given: git@github.com:briceburg/tools.git
      -> host: github.com path: briceburg/tools.git
    given: https://me@gmail.com:12345@my.site.com:443/p/a/t/h
      -> host: my.site.com path: p/a/t/h